Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40b7bd1c14 | ||
|
|
1c334fcce6 | ||
|
|
fd59b93463 |
@@ -2,16 +2,45 @@
|
||||
|
||||
You are implementing this repository under a frozen architecture generation. **Capability to edit files is not authority to change architecture.**
|
||||
|
||||
## Durable project principles
|
||||
|
||||
- Domovoy is a long-lived personal assistant, not a one-off chatbot or coding helper.
|
||||
- Current specialization is temporary; architecture is implementation-specific and revisable by approved architecture decisions.
|
||||
- Codex is an implementation capability, not architectural owner by default.
|
||||
- Model/provider is implementation infrastructure, not Domovoy identity.
|
||||
- Human reality claims are only claims until supported.
|
||||
- Human intent/preferences are strong evidence, even when not external facts.
|
||||
- UNKNOWN is a valid state and must remain explicit.
|
||||
- Reversible/low-risk safety is required before irreversible action.
|
||||
- Deterministic reflex is optimization, not final semantic truth.
|
||||
- Capability, transport, and implementation are separate concerns.
|
||||
- Capability usage requires declared evidence and observed capability gap handling.
|
||||
- LLM cost/brand/availability is evidence, not a blanket truth rule.
|
||||
- Experience is not learning by itself; only measured behavior change becomes evidence.
|
||||
- Normal user interaction must stay zero-ops.
|
||||
- Live configuration and canonical state should be portable and bootstrap-only environment should stay minimal.
|
||||
- Production continuity must include recovery and provenance across model/binding/config/loader/operator changes.
|
||||
- Do not overbuild future systems for current milestone.
|
||||
|
||||
## Mandatory startup sequence
|
||||
|
||||
Before any non-trivial change:
|
||||
|
||||
1. Read `architecture/ARCHITECTURE_CONSTITUTION.md`.
|
||||
2. Read `architecture/CORE_SEMANTIC_MODEL_V1.md`.
|
||||
3. Read `architecture/architecture-registry.yaml` and `architecture/architecture-registry.json`.
|
||||
4. Read `development/CODEX_DEVELOPMENT_CONTRACT.md`.
|
||||
5. Read the active file under `dev/tasks/`.
|
||||
6. Run `go run ./cmd/devctl preflight -task <task-file>` for Tier B+ tasks when available.
|
||||
1. Inspect git branch, status, and recent ancestry (at least `git status`, `git rev-parse`, `git log -1`).
|
||||
2. Read `AGENTS.md`.
|
||||
3. Read `architecture/ARCHITECTURE_CONSTITUTION.md`.
|
||||
4. Read `architecture/CORE_SEMANTIC_MODEL_V1.md`.
|
||||
5. Read `architecture/architecture-registry.yaml` and `architecture/architecture-registry.json`.
|
||||
6. Read `development/CODEX_DEVELOPMENT_CONTRACT.md`.
|
||||
7. Read `dev/ACTIVE_TASK.json`.
|
||||
8. Read the current task packet under `dev/tasks/` referenced by `ACTIVE_TASK`.
|
||||
9. Read relevant accepted ADRs from `architecture/adrs`.
|
||||
10. Inspect `docs/PROJECT_STATUS.md` and current evidence artifacts for checkpoint continuity.
|
||||
11. Inspect current runtime evidence/check commands before implementation.
|
||||
12. Run `go run ./cmd/devctl preflight -task <task-file>` for Tier B+ tasks.
|
||||
13. Then proceed with implementation.
|
||||
|
||||
For a fresh session, this sequence is the required cold-start reconstruction path and must be documented in the task checkpoint.
|
||||
|
||||
If documents conflict, authority order is:
|
||||
|
||||
@@ -72,3 +101,32 @@ State:
|
||||
- assumptions;
|
||||
- findings not changed;
|
||||
- gaps or limitations.
|
||||
|
||||
## Session reconstruction checkpoint (Durable memory)
|
||||
|
||||
Before leaving a DEV, include machine-readable checkpoint facts in:
|
||||
|
||||
- `dev/ACTIVE_TASK.json`
|
||||
- active task packet under `dev/tasks/`
|
||||
- `docs/PROJECT_STATUS.md`
|
||||
- `docs/COLD_START_AND_DURABLE_CHECKPOINT.md`
|
||||
|
||||
The checkpoint must explicitly carry:
|
||||
|
||||
- `STATUS`
|
||||
- `CURRENT_PHASE`
|
||||
- `COMPLETED`
|
||||
- `IN_PROGRESS`
|
||||
- `NOT_STARTED`
|
||||
- `BLOCKERS`
|
||||
- `IMPORTANT_FINDINGS`
|
||||
- `EVIDENCE_OBTAINED`
|
||||
- `LAST_VERIFIED_COMMIT`
|
||||
- `NEXT_ACTION`
|
||||
|
||||
No product behavior or canonical semantic changes are required for this milestone.
|
||||
|
||||
The fresh-session recovery procedure and evidence expectations are documented in:
|
||||
`docs/COLD_START_AND_DURABLE_CHECKPOINT.md`.
|
||||
|
||||
Execution-window exhaustion is continuation, not failure; do not mark a task done only because a session ends.
|
||||
|
||||
@@ -32,29 +32,6 @@ func TestChatPageServedWithoutInternalIDs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicSurfacesSeparateChatAndConsole(t *testing.T) {
|
||||
handler, err := newHandler("http://core", "http://continuity", "http://commit", http.DefaultClient, "http://cognitive")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
path string
|
||||
must string
|
||||
mustNot string
|
||||
}{
|
||||
{path: "/", must: "Напишите Домовому обычное сообщение", mustNot: "context-preset"},
|
||||
{path: "/admin", must: "context-preset", mustNot: "Напишите Домовому обычное сообщение"},
|
||||
{path: "/chat", must: "Напишите Домовому обычное сообщение", mustNot: "context-preset"},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, test.path, nil))
|
||||
body := recorder.Body.String()
|
||||
if recorder.Code != http.StatusOK || !strings.Contains(body, test.must) || strings.Contains(body, test.mustNot) {
|
||||
t.Fatalf("%s response=%d contains expected=%t forbidden=%t", test.path, recorder.Code, strings.Contains(body, test.must), strings.Contains(body, test.mustNot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessageUsesCoreAndLocalOnlyCognitiveGateway(t *testing.T) {
|
||||
var cognitiveRequest cognitive.GatewayAdvisoryRequest
|
||||
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -112,12 +112,7 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/" {
|
||||
serveEmbeddedHTML(w, "chat.html")
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/admin" {
|
||||
serveEmbeddedHTML(w, "index.html")
|
||||
return
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
}
|
||||
if r.URL.Path == "/chat" {
|
||||
r = r.Clone(r.Context())
|
||||
@@ -127,17 +122,6 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
g.static.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func serveEmbeddedHTML(w http.ResponseWriter, name string) {
|
||||
body, err := staticFiles.ReadFile("static/" + name)
|
||||
if err != nil {
|
||||
http.Error(w, "page unavailable", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
|
||||
type browserAdvisoryRequest struct {
|
||||
Message string `json:"message"`
|
||||
SessionID string `json:"session_id"`
|
||||
|
||||
@@ -229,21 +229,10 @@ func TestConsoleAndSecurityHeaders(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin", nil)
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "Домовой") ||
|
||||
!strings.Contains(response.Body.String(), "context-preset") ||
|
||||
!strings.Contains(response.Body.String(), "context-state") ||
|
||||
!strings.Contains(response.Body.String(), "copy-context") ||
|
||||
!strings.Contains(response.Body.String(), "К содержимому") ||
|
||||
!strings.Contains(response.Body.String(), "aria-current=\"location\"") ||
|
||||
!strings.Contains(response.Body.String(), "section-nav-link") ||
|
||||
!strings.Contains(response.Body.String(), "data-page=\"overview\"") ||
|
||||
!strings.Contains(response.Body.String(), "data-page=\"diagnostics\"") ||
|
||||
!strings.Contains(response.Body.String(), "data-page=\"operations\"") ||
|
||||
!strings.Contains(response.Body.String(), "data-page=\"learning\"") ||
|
||||
!strings.Contains(response.Body.String(), "data-page=\"settings\"") ||
|
||||
!strings.Contains(response.Body.String(), "satisfaction-dialog") || !strings.Contains(response.Body.String(), "Подтверждаю переход") ||
|
||||
!strings.Contains(response.Body.String(), "action-history") || !strings.Contains(response.Body.String(), "Журнал действий") ||
|
||||
!strings.Contains(response.Body.String(), "advisory-form") || !strings.Contains(response.Body.String(), "Спросить Домового") ||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
:root {
|
||||
--ink: #eef2ec;
|
||||
--muted: #a7b2aa;
|
||||
--faint: #718078;
|
||||
--base: #0f1512;
|
||||
--panel: #17201b;
|
||||
--panel-high: #202b25;
|
||||
--line: #34423a;
|
||||
--line-soft: #26332c;
|
||||
--ink: #e9eee7;
|
||||
--muted: #8c9a91;
|
||||
--faint: #58645d;
|
||||
--base: #0b100e;
|
||||
--panel: #101714;
|
||||
--panel-high: #151e1a;
|
||||
--line: #2a352f;
|
||||
--line-soft: #1d2822;
|
||||
--signal: #c9a85f;
|
||||
--signal-bright: #e7c97b;
|
||||
--good: #78c6a3;
|
||||
@@ -21,15 +21,13 @@
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
[hidden] { display: none !important; }
|
||||
html { background: var(--base); color: var(--ink); font-family: var(--sans); scroll-behavior: smooth; }
|
||||
body { margin: 0; min-width: 320px; min-height: 100vh; background: radial-gradient(circle at 73% -10%, rgba(83, 123, 101, .13), transparent 38%), var(--base); }
|
||||
html { background: var(--base); color: var(--ink); font-family: var(--sans); }
|
||||
body { margin: 0; min-width: 320px; min-height: 100vh; background: radial-gradient(circle at 73% -10%, rgba(83, 123, 101, .15), transparent 38%), var(--base); }
|
||||
button, input, textarea { font: inherit; }
|
||||
button { color: inherit; }
|
||||
button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible { outline: 2px solid var(--signal-bright); outline-offset: 3px; }
|
||||
.skip-link { position: fixed; top: 10px; left: 10px; z-index: 30; padding: 10px 13px; color: #11150f; background: var(--signal-bright); font: 600 10px/1 var(--mono); text-decoration: none; transform: translateY(-160%); transition: transform .18s; }
|
||||
.skip-link:focus { transform: translateY(0); }
|
||||
button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--signal-bright); outline-offset: 3px; }
|
||||
|
||||
.grain { position: fixed; inset: 0; pointer-events: none; opacity: .045; z-index: 10; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); }
|
||||
.grain { position: fixed; inset: 0; pointer-events: none; opacity: .18; z-index: 10; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); }
|
||||
.eyebrow, .rail-index { margin: 0 0 12px; color: var(--signal); font: 600 11px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; }
|
||||
|
||||
.login-shell { min-height: 100vh; display: grid; place-items: center; padding: 32px 20px; position: relative; overflow: hidden; }
|
||||
@@ -65,9 +63,6 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
|
||||
.wordmark strong { font: 400 20px/1 var(--serif); }
|
||||
.wordmark small { margin-top: 4px; color: var(--muted); font: 9px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; }
|
||||
.topbar-actions { display: flex; align-items: center; gap: 10px; }
|
||||
.topbar-nav { display: flex; align-items: center; gap: 4px; margin-right: 8px; }
|
||||
.topbar-nav a { padding: 9px 10px; color: var(--muted); text-decoration: none; font: 600 9px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
|
||||
.topbar-nav a:hover, .topbar-nav a:focus-visible { color: var(--ink); background: var(--panel-high); outline: 0; }
|
||||
.connection { margin-right: 12px; color: var(--muted); font: 10px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
|
||||
.connection i { width: 7px; height: 7px; display: inline-block; margin-right: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 14px rgba(120, 198, 163, .6); }
|
||||
.connection.offline i { background: var(--bad); box-shadow: none; }
|
||||
@@ -78,55 +73,17 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
|
||||
.context-rail h2 { margin: 0; font: 400 29px/1.1 var(--serif); }
|
||||
.rail-copy { margin: 14px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
||||
.context-form { display: grid; }
|
||||
.context-status-row { margin-bottom: 16px; padding: 10px 11px; display: flex; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--line-soft); background: rgba(120,198,163,.04); }
|
||||
.context-status-label { color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .1em; }
|
||||
.context-status-row strong { color: var(--warn); font: 600 9px/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; }
|
||||
.context-status-row strong[data-state="ready"] { color: var(--good); }
|
||||
.context-presets { display: grid; gap: 7px; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid var(--line-soft); }
|
||||
.context-presets label { margin: 0; }
|
||||
.context-presets select { width: 100%; min-height: 38px; padding: 0 9px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); background: #0b110e; font: 10px/1 var(--mono); }
|
||||
.context-presets small { color: var(--faint); font-size: 10px; line-height: 1.35; }
|
||||
.context-field { display: grid; gap: 7px; }
|
||||
.context-field + .context-field { margin-top: 13px; }
|
||||
.context-field label { margin: 0; }
|
||||
.context-field label span { color: var(--signal); font-size: 8px; letter-spacing: .06em; }
|
||||
.context-input-row { display: grid; grid-template-columns: minmax(0, 1fr) 34px; gap: 5px; }
|
||||
.context-input-row input { min-width: 0; }
|
||||
.copy-context { width: 34px; height: 40px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--muted); background: transparent; cursor: pointer; }
|
||||
.copy-context:hover { color: var(--ink); border-color: var(--faint); background: var(--panel-high); }
|
||||
.id-actions { min-height: 31px; display: flex; align-items: center; gap: 7px; margin-top: 12px; }
|
||||
.id-action { min-height: 30px; padding: 0 9px; border: 1px solid var(--line-soft); border-radius: var(--radius); color: var(--muted); background: transparent; cursor: pointer; font: 9px/1 var(--mono); letter-spacing: .04em; }
|
||||
.id-action:hover { color: var(--ink); border-color: var(--signal); background: rgba(201,168,95,.07); }
|
||||
.icon-id-action { width: 30px; padding: 0; color: var(--faint); }
|
||||
.context-form label { margin-top: 15px; }
|
||||
.context-form label:first-child { margin-top: 0; }
|
||||
.context-form input { height: 40px; padding: 0 10px; font: 11px/1 var(--mono); }
|
||||
.context-hint { margin: 12px 0 0; color: var(--faint); font-size: 10px; line-height: 1.45; }
|
||||
.advanced-context { margin-top: 17px; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
|
||||
.advanced-context summary { padding: 12px 0; color: var(--muted); cursor: pointer; list-style: none; font: 600 9px/1.2 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
|
||||
.advanced-context summary::before { content: "+"; display: inline-block; width: 18px; color: var(--signal); font-size: 13px; }
|
||||
.advanced-context[open] summary::before { content: "−"; }
|
||||
.advanced-context > div { padding: 0 0 10px; }
|
||||
.advanced-context label { margin-top: 13px; }
|
||||
.context-actions { display: grid; gap: 8px; }
|
||||
.context-actions .secondary-button { margin-top: 18px; }
|
||||
.context-actions .text-button { margin: 0; justify-self: start; padding-inline: 0; }
|
||||
.secondary-button { height: 42px; margin-top: 20px; color: #11150f; background: var(--signal); font-size: 12px; }
|
||||
.section-nav { display: grid; gap: 3px; margin-top: 4px; }
|
||||
.section-nav-link { display: grid; grid-template-columns: 28px 1fr; gap: 2px 10px; padding: 11px 10px; border-left: 2px solid transparent; color: var(--muted); text-decoration: none; transition: background .18s, border-color .18s, color .18s; }
|
||||
.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active, .section-nav-link[aria-current="location"] { border-left-color: var(--signal); color: var(--ink); background: rgba(201,168,95,.07); outline: 0; }
|
||||
.section-nav-link span { grid-row: 1 / span 2; color: var(--signal); font: 9px/1.4 var(--mono); }
|
||||
.section-nav-link b { font: 600 11px/1.2 var(--sans); }
|
||||
.section-nav-link small { color: var(--faint); font: 9px/1.2 var(--mono); }
|
||||
.session-stamp { margin-top: auto; padding-top: 20px; display: grid; gap: 6px; border-top: 1px solid var(--line); }
|
||||
.session-stamp span { margin-top: 8px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .14em; }
|
||||
.session-stamp strong { overflow: hidden; color: var(--muted); font: 10px/1.4 var(--mono); text-overflow: ellipsis; }
|
||||
|
||||
.workspace { grid-column: 2; grid-row: 2; width: min(100%, 1740px); justify-self: start; padding: 42px clamp(24px, 3vw, 48px) 64px; min-width: 0; }
|
||||
.workspace > [data-page][hidden] { display: none !important; }
|
||||
.workspace > section { scroll-margin-top: 94px; }
|
||||
.workspace { grid-column: 2; grid-row: 2; padding: clamp(28px, 4vw, 58px); min-width: 0; }
|
||||
.workspace-head { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-bottom: 34px; }
|
||||
.workspace-head h1 { margin: 0; font: 400 clamp(34px, 4vw, 50px)/1 var(--serif); letter-spacing: -.035em; }
|
||||
.workspace-head h1 { margin: 0; font: 400 clamp(36px, 5vw, 58px)/1 var(--serif); letter-spacing: -.035em; }
|
||||
.last-update { color: var(--faint); font: 10px/1.4 var(--mono); text-align: right; }
|
||||
.signal-strip { display: grid; grid-template-columns: repeat(4, 1fr); margin-bottom: 18px; border: 1px solid var(--line); background: var(--panel); }
|
||||
.signal-strip div { padding: 16px 18px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-right: 1px solid var(--line); }
|
||||
@@ -137,46 +94,6 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
|
||||
.signal-strip strong[data-state="error"] { color: var(--bad); }
|
||||
.signal-strip strong[data-state="loading"] { color: var(--warn); animation: pulse 1.1s ease-in-out infinite; }
|
||||
|
||||
.diagnostics-panel, .settings-panel { margin-bottom: 18px; border: 1px solid var(--line); background: rgba(16,23,20,.82); }
|
||||
.diagnostics-panel { border-color: rgba(120,198,163,.32); box-shadow: inset 3px 0 0 rgba(120,198,163,.45); }
|
||||
.panel-heading { min-height: 68px; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid var(--line); }
|
||||
.panel-heading p { margin: 0 0 6px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .13em; text-transform: uppercase; }
|
||||
.panel-heading h2 { margin: 0; font: 400 23px/1 var(--serif); }
|
||||
.panel-heading-tools { display: flex; align-items: center; gap: 14px; }
|
||||
.panel-heading-tools > span, .settings-saved { color: var(--muted); font: 9px/1.3 var(--mono); letter-spacing: .06em; text-transform: uppercase; }
|
||||
.compact-button { width: auto; min-width: 142px; height: 38px; margin: 0; padding: 0 13px; }
|
||||
.diagnostic-intro { padding: 13px 18px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--line-soft); color: var(--muted); }
|
||||
.diagnostic-intro p { margin: 0; font-size: 11px; line-height: 1.45; }
|
||||
.diagnostic-pulse { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; background: var(--good); box-shadow: 0 0 12px rgba(120,198,163,.7); }
|
||||
.diagnostic-list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.diagnostic-list[aria-busy="true"] { opacity: .7; }
|
||||
.diagnostic-item { min-width: 0; min-height: 92px; padding: 14px 16px; display: grid; grid-template-columns: 9px 1fr auto; gap: 9px; align-items: start; border-right: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
|
||||
.diagnostic-item:nth-child(4n) { border-right: 0; }
|
||||
.diagnostic-item > i { width: 7px; height: 7px; margin-top: 4px; border-radius: 50%; background: var(--faint); }
|
||||
.diagnostic-item[data-state="ok"] > i { background: var(--good); box-shadow: 0 0 9px rgba(120,198,163,.6); }
|
||||
.diagnostic-item[data-state="error"] > i { background: var(--bad); }
|
||||
.diagnostic-item[data-state="loading"] > i { background: var(--warn); animation: pulse 1.1s ease-in-out infinite; }
|
||||
.diagnostic-item strong { display: block; color: var(--ink); font: 600 10px/1.3 var(--mono); }
|
||||
.diagnostic-item span { display: block; margin-top: 6px; color: var(--muted); font-size: 10px; line-height: 1.35; }
|
||||
.diagnostic-item em { color: var(--faint); font: normal 9px/1.2 var(--mono); text-transform: uppercase; }
|
||||
.diagnostic-item[data-state="ok"] em { color: var(--good); }
|
||||
.diagnostic-item[data-state="error"] em { color: var(--bad); }
|
||||
.settings-grid { padding: 18px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; }
|
||||
.setting-field, .setting-check { min-width: 0; display: grid; gap: 7px; color: var(--ink); }
|
||||
.setting-field > span, .setting-check b { font: 600 10px/1.2 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
|
||||
.setting-field small, .setting-check small { color: var(--muted); font-size: 11px; line-height: 1.4; }
|
||||
.setting-field select { width: 100%; min-height: 40px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); background: #0b110e; font: 11px/1 var(--mono); }
|
||||
.setting-check { grid-template-columns: 18px 1fr; align-items: start; gap: 10px; padding: 10px 12px; border: 1px solid var(--line-soft); }
|
||||
.setting-check input { width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--signal); }
|
||||
.setting-check span { display: grid; gap: 7px; }
|
||||
.settings-actions { display: flex; align-items: center; justify-content: flex-end; gap: 14px; grid-column: 1 / -1; padding-top: 2px; }
|
||||
.settings-actions .text-button { margin: 0; }
|
||||
html[data-density="compact"] .data-card { padding: 18px; }
|
||||
html[data-density="compact"] .data-card dl div { padding-block: 10px; }
|
||||
html[data-density="compact"] .activity-row { min-height: 58px; padding-block: 8px; }
|
||||
html[data-density="compact"] .learning-row { min-height: 54px; padding-block: 9px; }
|
||||
html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before, html[data-reduced-motion="true"] *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; }
|
||||
|
||||
.advisor-panel { margin-bottom: 18px; padding: 24px; display: grid; grid-template-columns: minmax(220px, .72fr) minmax(320px, 1.28fr); gap: 18px 34px; border: 1px solid rgba(201,168,95,.42); background: linear-gradient(125deg, rgba(201,168,95,.1), rgba(16,23,20,.88) 44%, rgba(12,20,17,.96)); box-shadow: inset 3px 0 0 rgba(201,168,95,.58); }
|
||||
.advisor-intro { align-self: center; }
|
||||
.advisor-intro > p { margin: 0 0 10px; color: var(--signal); font: 600 9px/1 var(--mono); letter-spacing: .15em; }
|
||||
@@ -361,9 +278,6 @@ html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before,
|
||||
.advisor-panel { grid-template-columns: 1fr; }
|
||||
.capability-card { grid-column: auto; }
|
||||
.prospective-card { grid-column: auto; }
|
||||
.diagnostic-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.diagnostic-item:nth-child(4n) { border-right: 1px solid var(--line-soft); }
|
||||
.diagnostic-item:nth-child(2n) { border-right: 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
@@ -371,25 +285,14 @@ html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before,
|
||||
.console { display: block; }
|
||||
.topbar { height: 66px; padding: 0 14px; }
|
||||
.wordmark small, .connection, .text-button { display: none; }
|
||||
.topbar-nav { display: none; }
|
||||
.context-rail { padding: 24px 18px; border-right: 0; border-bottom: 1px solid var(--line); }
|
||||
.rail-copy { max-width: 48ch; }
|
||||
.session-stamp { margin-top: 8px; grid-template-columns: auto 1fr; align-items: center; }
|
||||
.session-stamp span { margin: 0; }
|
||||
.workspace { padding: 30px 16px 50px; }
|
||||
.section-nav { display: flex; gap: 6px; margin: 10px -18px -24px; padding: 10px 18px; overflow-x: auto; border-top: 1px solid var(--line-soft); background: #0e1512; scrollbar-width: thin; }
|
||||
.section-nav-link { min-width: 142px; padding: 10px; border-left: 0; border-bottom: 2px solid transparent; }
|
||||
.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active, .section-nav-link[aria-current="location"] { border-left-color: transparent; border-bottom-color: var(--signal); }
|
||||
.workspace-head { align-items: start; flex-direction: column; }
|
||||
.last-update { text-align: left; }
|
||||
.signal-strip { grid-template-columns: 1fr; }
|
||||
.panel-heading { align-items: flex-start; flex-direction: column; }
|
||||
.panel-heading-tools { width: 100%; justify-content: space-between; }
|
||||
.diagnostic-list { grid-template-columns: 1fr; }
|
||||
.diagnostic-item, .diagnostic-item:nth-child(2n), .diagnostic-item:nth-child(4n) { border-right: 0; }
|
||||
.settings-grid { grid-template-columns: 1fr; padding: 16px; }
|
||||
.settings-actions { grid-column: auto; align-items: stretch; flex-direction: column; }
|
||||
.settings-actions .primary-button { width: 100%; }
|
||||
.signal-strip div { border-right: 0; border-bottom: 1px solid var(--line); }
|
||||
.advisor-panel { padding: 20px 16px; gap: 20px; }
|
||||
.advisor-controls { align-items: stretch; flex-direction: column; }
|
||||
|
||||
@@ -7,42 +7,14 @@ const advisoryForm = byId("advisory-form");
|
||||
const loginError = byId("login-error");
|
||||
const toast = byId("toast");
|
||||
const contextFields = ["session-id", "workspace-id", "task-id", "prospective-id", "capability-id"];
|
||||
const settingsStorageKey = "domovoy.creator.settings";
|
||||
const contextPresetsStorageKey = "domovoy.creator.context.presets";
|
||||
let sessionToken = sessionStorage.getItem("domovoy.creator.session") || "";
|
||||
let toastTimer;
|
||||
let refreshTimer;
|
||||
let currentTask = null;
|
||||
let currentProspective = null;
|
||||
let pendingCommand = null;
|
||||
let pendingNotification = null;
|
||||
let lastAuthorizedActionID = "";
|
||||
let advisoryPolicy = {Mode: "deterministic", ExternalEgress: false, AcknowledgementRequired: false};
|
||||
let uiSettings = {density: "comfortable", refresh: "off", motion: false};
|
||||
|
||||
const pageNavLinks = Array.from(document.querySelectorAll(".section-nav-link, .topbar-nav a"));
|
||||
const pageViews = Array.from(document.querySelectorAll("[data-page]"));
|
||||
const cabinetPages = ["overview", "diagnostics", "operations", "learning", "settings"];
|
||||
|
||||
function setActivePage(id, updateHistory = false) {
|
||||
const page = cabinetPages.includes(id) ? id : "overview";
|
||||
pageViews.forEach((view) => { view.hidden = view.dataset.page !== page; });
|
||||
document.body.dataset.page = page;
|
||||
pageNavLinks.forEach((link) => {
|
||||
const active = link.getAttribute("href") === `#${page}`;
|
||||
link.classList.toggle("active", active);
|
||||
if (active) link.setAttribute("aria-current", "location");
|
||||
else link.removeAttribute("aria-current");
|
||||
});
|
||||
if (updateHistory && window.location.hash !== `#${page}`) window.history.pushState({}, "", `#${page}`);
|
||||
}
|
||||
|
||||
pageNavLinks.forEach((link) => link.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
setActivePage(link.getAttribute("href").slice(1), true);
|
||||
}));
|
||||
window.addEventListener("popstate", () => setActivePage(window.location.hash.slice(1)));
|
||||
setActivePage(window.location.hash.slice(1));
|
||||
|
||||
function setText(id, value, fallback = "—") {
|
||||
byId(id).textContent = value === undefined || value === null || value === "" ? fallback : String(value);
|
||||
@@ -67,18 +39,6 @@ function notify(message) {
|
||||
toastTimer = setTimeout(() => { toast.hidden = true; }, 4200);
|
||||
}
|
||||
|
||||
function safeClientError(error) {
|
||||
const message = error && typeof error.message === "string" ? error.message : "";
|
||||
switch (message) {
|
||||
case "creator authentication denied":
|
||||
return "Логин или пароль не принят.";
|
||||
case "Сессия отклонена":
|
||||
return "Сессия истекла. Войдите снова.";
|
||||
default:
|
||||
return "Операция недоступна. Попробуйте ещё раз.";
|
||||
}
|
||||
}
|
||||
|
||||
async function request(path, options = {}) {
|
||||
const headers = new Headers(options.headers || {});
|
||||
if (sessionToken) headers.set("Authorization", `Bearer ${sessionToken}`);
|
||||
@@ -90,9 +50,7 @@ async function request(path, options = {}) {
|
||||
}
|
||||
if (!response.ok) {
|
||||
const detail = (await response.text()).trim();
|
||||
const error = new Error(detail || `Ошибка ${response.status}`);
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
throw new Error(detail || `Ошибка ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
@@ -106,57 +64,15 @@ function restoreContext() {
|
||||
} catch (_) {
|
||||
localStorage.removeItem("domovoy.creator.context");
|
||||
}
|
||||
renderContextState();
|
||||
renderContextPresets();
|
||||
}
|
||||
|
||||
function saveContext() {
|
||||
const saved = {};
|
||||
contextFields.forEach((id) => { saved[id] = byId(id).value.trim(); });
|
||||
localStorage.setItem("domovoy.creator.context", JSON.stringify(saved));
|
||||
renderContextState();
|
||||
return saved;
|
||||
}
|
||||
|
||||
function readContextPresets() {
|
||||
try {
|
||||
const value = JSON.parse(localStorage.getItem(contextPresetsStorageKey) || "[]");
|
||||
return Array.isArray(value) ? value.filter((item) => item && item.session && item.workspace) : [];
|
||||
} catch (_) {
|
||||
localStorage.removeItem(contextPresetsStorageKey);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function renderContextState() {
|
||||
const state = byId("context-state");
|
||||
if (!state) return;
|
||||
const ready = Boolean(byId("session-id").value.trim() && byId("workspace-id").value.trim());
|
||||
state.textContent = ready ? "готов к проверке" : "не выбран";
|
||||
state.dataset.state = ready ? "ready" : "empty";
|
||||
}
|
||||
|
||||
function renderContextPresets() {
|
||||
const select = byId("context-preset");
|
||||
if (!select) return;
|
||||
const presets = readContextPresets();
|
||||
select.replaceChildren(new Option("Выберите сохранённый набор…", ""));
|
||||
presets.forEach((preset, index) => {
|
||||
const session = preset.session.length > 18 ? `${preset.session.slice(0, 8)}…${preset.session.slice(-6)}` : preset.session;
|
||||
const workspace = preset.workspace.length > 18 ? `${preset.workspace.slice(0, 8)}…${preset.workspace.slice(-6)}` : preset.workspace;
|
||||
select.appendChild(new Option(`${session} · ${workspace}`, String(index)));
|
||||
});
|
||||
}
|
||||
|
||||
function rememberContextPreset(context) {
|
||||
if (!context["session-id"] || !context["workspace-id"]) return;
|
||||
const next = {session: context["session-id"], workspace: context["workspace-id"], savedAt: Date.now()};
|
||||
const presets = readContextPresets().filter((item) => item.session !== next.session || item.workspace !== next.workspace);
|
||||
presets.unshift(next);
|
||||
localStorage.setItem(contextPresetsStorageKey, JSON.stringify(presets.slice(0, 5)));
|
||||
renderContextPresets();
|
||||
}
|
||||
|
||||
async function showConsole() {
|
||||
const session = await request("/api/session/current");
|
||||
setText("session-actor", session.ActorID);
|
||||
@@ -164,7 +80,6 @@ async function showConsole() {
|
||||
setText("session-expiry", expiry);
|
||||
loginView.hidden = true;
|
||||
consoleView.hidden = false;
|
||||
restoreSettings();
|
||||
restoreContext();
|
||||
await loadAdvisoryPolicy();
|
||||
const context = saveContext();
|
||||
@@ -173,13 +88,10 @@ async function showConsole() {
|
||||
loadActionHistory();
|
||||
loadLearning();
|
||||
}
|
||||
runDiagnostics();
|
||||
}
|
||||
|
||||
function endSession(message = "Сессия завершена.") {
|
||||
sessionToken = "";
|
||||
clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
sessionStorage.removeItem("domovoy.creator.session");
|
||||
consoleView.hidden = true;
|
||||
loginView.hidden = false;
|
||||
@@ -216,7 +128,7 @@ loginForm.addEventListener("submit", async (event) => {
|
||||
byId("creator-password").value = "";
|
||||
await showConsole();
|
||||
} catch (error) {
|
||||
loginError.textContent = safeClientError(error);
|
||||
loginError.textContent = error.message === "creator authentication denied" ? "Логин или пароль не принят." : error.message;
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.firstElementChild.textContent = "Открыть консоль";
|
||||
@@ -232,129 +144,14 @@ byId("toggle-secret").addEventListener("click", () => {
|
||||
|
||||
contextForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const context = saveContext();
|
||||
rememberContextPreset(context);
|
||||
saveContext();
|
||||
loadDashboard();
|
||||
});
|
||||
|
||||
for (const input of [byId("session-id"), byId("workspace-id")]) {
|
||||
input.addEventListener("input", renderContextState);
|
||||
}
|
||||
|
||||
byId("context-preset").addEventListener("change", (event) => {
|
||||
const index = Number(event.target.value);
|
||||
if (!Number.isInteger(index)) return;
|
||||
const preset = readContextPresets()[index];
|
||||
if (!preset) return;
|
||||
byId("session-id").value = preset.session;
|
||||
byId("workspace-id").value = preset.workspace;
|
||||
renderContextState();
|
||||
loadDashboard();
|
||||
notify("Контекст из недавнего набора загружен.");
|
||||
});
|
||||
|
||||
for (const button of document.querySelectorAll("[data-copy-context]")) {
|
||||
button.addEventListener("click", async () => {
|
||||
const value = byId(button.dataset.copyContext).value.trim();
|
||||
if (!value) {
|
||||
notify("Сначала укажите идентификатор.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
notify("Идентификатор скопирован.");
|
||||
} catch (_) {
|
||||
notify("Не удалось скопировать идентификатор.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const button of document.querySelectorAll("[data-copy-value], [data-use-value]")) {
|
||||
button.addEventListener("click", async () => {
|
||||
const source = byId(button.dataset.copyValue || button.dataset.useValue);
|
||||
const value = source?.textContent.trim() || "";
|
||||
if (!value || value === "—") {
|
||||
notify("Сначала загрузите значение.");
|
||||
return;
|
||||
}
|
||||
if (button.dataset.useValue) {
|
||||
const target = byId(button.dataset.targetId);
|
||||
if (!target) return;
|
||||
target.value = value;
|
||||
document.querySelector(".advanced-context").open = true;
|
||||
saveContext();
|
||||
notify("ID подставлен в расширенную проверку.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
notify("ID скопирован.");
|
||||
} catch (_) {
|
||||
notify("Не удалось скопировать ID.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
byId("reset-context").addEventListener("click", () => {
|
||||
contextFields.forEach((id) => { byId(id).value = id === "capability-id" ? "cognitive-proposal" : ""; });
|
||||
localStorage.removeItem("domovoy.creator.context");
|
||||
loadDashboard();
|
||||
notify("Рабочий контекст сброшен.");
|
||||
});
|
||||
|
||||
function restoreSettings() {
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem(settingsStorageKey) || "{}");
|
||||
uiSettings = {
|
||||
density: ["comfortable", "compact"].includes(saved.density) ? saved.density : "comfortable",
|
||||
refresh: ["off", "30", "60", "300"].includes(saved.refresh) ? saved.refresh : "off",
|
||||
motion: saved.motion === true,
|
||||
};
|
||||
} catch (_) {
|
||||
uiSettings = {density: "comfortable", refresh: "off", motion: false};
|
||||
}
|
||||
byId("setting-density").value = uiSettings.density;
|
||||
byId("setting-refresh").value = uiSettings.refresh;
|
||||
byId("setting-motion").checked = uiSettings.motion;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
function applySettings() {
|
||||
document.documentElement.dataset.density = uiSettings.density;
|
||||
document.documentElement.dataset.reducedMotion = uiSettings.motion ? "true" : "false";
|
||||
clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
if (sessionToken && uiSettings.refresh !== "off") {
|
||||
refreshTimer = setInterval(() => loadDashboard(), Number(uiSettings.refresh) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
byId("settings-form").addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
uiSettings = {
|
||||
density: byId("setting-density").value,
|
||||
refresh: byId("setting-refresh").value,
|
||||
motion: byId("setting-motion").checked,
|
||||
};
|
||||
localStorage.setItem(settingsStorageKey, JSON.stringify(uiSettings));
|
||||
applySettings();
|
||||
setText("settings-saved", "Сохранено только в этом браузере");
|
||||
notify("Настройки кабинета сохранены.");
|
||||
});
|
||||
|
||||
byId("reset-settings").addEventListener("click", () => {
|
||||
uiSettings = {density: "comfortable", refresh: "off", motion: false};
|
||||
localStorage.removeItem(settingsStorageKey);
|
||||
restoreSettings();
|
||||
setText("settings-saved", "Восстановлены значения по умолчанию");
|
||||
notify("Настройки сброшены.");
|
||||
});
|
||||
|
||||
byId("refresh-all").addEventListener("click", loadDashboard);
|
||||
byId("refresh-actions").addEventListener("click", loadActionHistory);
|
||||
byId("refresh-learning").addEventListener("click", loadLearning);
|
||||
byId("logout").addEventListener("click", () => endSession());
|
||||
byId("run-diagnostics").addEventListener("click", runDiagnostics);
|
||||
|
||||
advisoryForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -388,7 +185,7 @@ advisoryForm.addEventListener("submit", async (event) => {
|
||||
byId("advisory-result").hidden = false;
|
||||
} catch (error) {
|
||||
setText("advisory-state", "ОШИБКА");
|
||||
setText("advisory-text", safeClientError(error));
|
||||
setText("advisory-text", error.message);
|
||||
byId("advisory-result").hidden = false;
|
||||
} finally {
|
||||
byId("advisory-egress-ack").checked = false;
|
||||
@@ -449,57 +246,6 @@ async function loadDashboard() {
|
||||
setText("last-update", `Обновлено ${new Intl.DateTimeFormat("ru-RU", {hour: "2-digit", minute: "2-digit", second: "2-digit"}).format(new Date())}`);
|
||||
}
|
||||
|
||||
async function runDiagnostics() {
|
||||
const list = byId("diagnostic-list");
|
||||
const summary = byId("diagnostic-summary");
|
||||
const checks = [
|
||||
["Сессия", "identity", () => request("/api/session/current")],
|
||||
["Политика совета", "advisory", () => request("/api/advisory-policy")],
|
||||
["Когнитивная возможность", "capability", () => request("/api/capabilities/cognitive-proposal/health")],
|
||||
["Сегодня", "today", () => request("/api/today")],
|
||||
["Внимание", "attention", () => request("/api/attention")],
|
||||
["Действия", "actions", () => request("/api/actions?limit=1")],
|
||||
["Learning evidence", "learning", () => request("/api/learning/recent?limit=1")],
|
||||
];
|
||||
list.setAttribute("aria-busy", "true");
|
||||
list.replaceChildren();
|
||||
checks.forEach(([label, key]) => list.append(createDiagnosticItem(label, key, "loading")));
|
||||
setText("diagnostic-summary", "Проверяю…");
|
||||
const results = await Promise.all(checks.map(async ([label, key, run]) => {
|
||||
const started = performance.now();
|
||||
try {
|
||||
await run();
|
||||
return {label, key, state: "ok", detail: `Ответ получен · ${Math.round(performance.now() - started)} мс`};
|
||||
} catch (error) {
|
||||
const detail = error?.status === 400 ? "Нужны параметры рабочего контекста" : error?.status === 401 ? "Сессия истекла" : error?.status >= 500 ? "Сервис временно недоступен" : "Проверка не пройдена";
|
||||
return {label, key, state: "error", detail};
|
||||
}
|
||||
}));
|
||||
list.replaceChildren(...results.map((result) => createDiagnosticItem(result.label, result.key, result.state, result.detail)));
|
||||
list.setAttribute("aria-busy", "false");
|
||||
const passed = results.filter((result) => result.state === "ok").length;
|
||||
setText("diagnostic-summary", `${passed}/${results.length} проверок пройдено`);
|
||||
}
|
||||
|
||||
function createDiagnosticItem(label, key, state, detail = "Проверяю…") {
|
||||
const item = document.createElement("article");
|
||||
item.className = "diagnostic-item";
|
||||
item.dataset.state = state;
|
||||
item.dataset.check = key;
|
||||
const marker = document.createElement("i");
|
||||
marker.setAttribute("aria-hidden", "true");
|
||||
const copy = document.createElement("div");
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = label;
|
||||
const description = document.createElement("span");
|
||||
description.textContent = detail;
|
||||
copy.append(title, description);
|
||||
const status = document.createElement("em");
|
||||
status.textContent = state === "ok" ? "OK" : state === "error" ? "ошибка" : "…";
|
||||
item.append(marker, copy, status);
|
||||
return item;
|
||||
}
|
||||
|
||||
async function loadLearning() {
|
||||
const experienceList = byId("experience-list");
|
||||
const reflectionList = byId("reflection-list");
|
||||
@@ -526,7 +272,7 @@ async function loadLearning() {
|
||||
list.replaceChildren();
|
||||
const failed = document.createElement("p");
|
||||
failed.className = "register-empty activity-error";
|
||||
failed.textContent = safeClientError(error);
|
||||
failed.textContent = error.message;
|
||||
list.append(failed);
|
||||
});
|
||||
setText("learning-count", "ОШИБКА");
|
||||
@@ -625,7 +371,7 @@ async function loadActionHistory() {
|
||||
list.replaceChildren();
|
||||
const failed = document.createElement("p");
|
||||
failed.className = "register-empty activity-error";
|
||||
failed.textContent = safeClientError(error);
|
||||
failed.textContent = error.message;
|
||||
list.append(failed);
|
||||
setText("action-count", "ОШИБКА");
|
||||
} finally {
|
||||
@@ -684,7 +430,7 @@ async function reconcileHistoryAction(actionID, button) {
|
||||
await loadActionHistory();
|
||||
if (currentProspective) await loadProspectiveRegister(currentProspective.ID);
|
||||
} catch (error) {
|
||||
notify(safeClientError(error));
|
||||
notify(error.message);
|
||||
button.disabled = false;
|
||||
button.textContent = "Сверить";
|
||||
}
|
||||
@@ -704,7 +450,7 @@ async function loadFocus(query) {
|
||||
} catch (error) {
|
||||
setSignal("focus", "ошибка", "error");
|
||||
setBadge("focus", "НЕДОСТУПЕН", "bad");
|
||||
setText("focus-message", safeClientError(error));
|
||||
setText("focus-message", error.message);
|
||||
byId("connection-state").classList.add("offline");
|
||||
}
|
||||
}
|
||||
@@ -726,7 +472,7 @@ async function loadTask(taskID, query) {
|
||||
currentTask = null;
|
||||
setSignal("task", "отклонена", "error");
|
||||
setBadge("task", "ВНЕ ФОКУСА", "bad");
|
||||
setText("task-message", safeClientError(error));
|
||||
setText("task-message", error.message);
|
||||
updateActionReadiness();
|
||||
}
|
||||
}
|
||||
@@ -765,12 +511,12 @@ async function loadProspectiveRegister(selectedID) {
|
||||
list.replaceChildren();
|
||||
const failed = document.createElement("p");
|
||||
failed.className = "register-empty";
|
||||
failed.textContent = safeClientError(error);
|
||||
failed.textContent = error.message;
|
||||
list.append(failed);
|
||||
setText("prospective-count", "ОШИБКА");
|
||||
setSignal("prospective", "ошибка", "error");
|
||||
setBadge("prospective", "НЕДОСТУПНА", "bad");
|
||||
setText("prospective-message", safeClientError(error));
|
||||
setText("prospective-message", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,7 +572,7 @@ async function loadProspective(prospectiveID) {
|
||||
setSignal("prospective", "ошибка", "error");
|
||||
setBadge("prospective", "НЕДОСТУПНА", "bad");
|
||||
setText("prospective-state", "UNKNOWN");
|
||||
setText("prospective-message", safeClientError(error));
|
||||
setText("prospective-message", error.message);
|
||||
updateActionReadiness();
|
||||
}
|
||||
}
|
||||
@@ -848,7 +594,7 @@ async function loadCapability(capabilityID) {
|
||||
setSignal("capability", "ошибка", "error");
|
||||
setBadge("capability", "НЕДОСТУПНА", "bad");
|
||||
setText("capability-state", "UNKNOWN");
|
||||
setText("capability-reason", safeClientError(error));
|
||||
setText("capability-reason", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,7 +700,7 @@ byId("confirm-notification").addEventListener("click", async () => {
|
||||
await loadActionHistory();
|
||||
notify(result.State === "SUCCEEDED" ? "Receiver принял уведомление." : "Исход отправки требует сверки.");
|
||||
} catch (error) {
|
||||
setText("notification-confirmation-error", safeClientError(error));
|
||||
setText("notification-confirmation-error", error.message);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = "Подтверждаю отправку";
|
||||
@@ -984,7 +730,7 @@ byId("confirm-satisfaction").addEventListener("click", async () => {
|
||||
notify(result.State === "SUCCEEDED" ? "Исполнение зафиксировано через Commit." : "Исход требует проверки.");
|
||||
await loadProspectiveRegister(command.ProspectiveItemID);
|
||||
} catch (error) {
|
||||
setText("confirmation-error", safeClientError(error));
|
||||
setText("confirmation-error", error.message);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = "Подтверждаю переход";
|
||||
@@ -1018,7 +764,7 @@ byId("reconcile-action").addEventListener("click", async () => {
|
||||
notify(result.State === "SUCCEEDED" ? "Исход подтверждён." : "Исход пока неизвестен.");
|
||||
if (currentProspective) await loadProspectiveRegister(currentProspective.ID);
|
||||
} catch (error) {
|
||||
notify(safeClientError(error));
|
||||
notify(error.message);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = "Проверить исход";
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
|
||||
<section id="chat-view" class="chat-view" hidden>
|
||||
<header class="chat-header">
|
||||
<a href="/" aria-label="Домовой">Домовой</a>
|
||||
<a href="/chat" aria-label="Домовой">Домовой</a>
|
||||
<nav class="human-nav" aria-label="Разделы Домового">
|
||||
<button class="nav-tab active" data-tab="chat-panel" type="button">Чат</button>
|
||||
<button class="nav-tab" data-tab="today-panel" type="button">Сегодня</button>
|
||||
<button class="nav-tab" data-tab="history-panel" type="button">История</button>
|
||||
<a href="/admin" class="operator-link">Консоль</a>
|
||||
<a href="/" class="operator-link">Оператор</a>
|
||||
</nav>
|
||||
<span id="active-focus">фокус: не выбран</span>
|
||||
<span id="session-state">сессия активна</span>
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="theme-color" content="#101714">
|
||||
<title>Домовой — консоль создателя</title>
|
||||
<link rel="stylesheet" href="/assets/console.css?v=20260821-context">
|
||||
<link rel="stylesheet" href="/assets/console.css">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#overview">К содержимому</a>
|
||||
<div class="grain" aria-hidden="true"></div>
|
||||
|
||||
<main id="login-view" class="login-shell">
|
||||
@@ -45,13 +44,6 @@
|
||||
</a>
|
||||
<div class="topbar-actions">
|
||||
<span id="connection-state" class="connection"><i></i>система доступна</span>
|
||||
<nav class="topbar-nav" aria-label="Разделы кабинета">
|
||||
<a href="#overview">Обзор</a>
|
||||
<a href="#diagnostics">Проверки</a>
|
||||
<a href="#operations">Операции</a>
|
||||
<a href="#learning">Опыт</a>
|
||||
<a href="#settings">Настройки</a>
|
||||
</nav>
|
||||
<button id="refresh-all" class="text-button" type="button">Обновить</button>
|
||||
<button id="logout" class="icon-button" type="button" aria-label="Завершить сессию" title="Выйти">↗</button>
|
||||
</div>
|
||||
@@ -64,46 +56,18 @@
|
||||
<p class="rail-copy">Укажите рабочий контекст. Он сохраняется только в этом браузере и не даёт дополнительных полномочий.</p>
|
||||
</div>
|
||||
<form id="context-form" class="context-form">
|
||||
<div class="context-status-row">
|
||||
<span class="context-status-label">ТЕКУЩИЙ КОНТЕКСТ</span>
|
||||
<strong id="context-state" data-state="empty">не выбран</strong>
|
||||
</div>
|
||||
<div class="context-presets">
|
||||
<label for="context-preset">Недавние наборы</label>
|
||||
<select id="context-preset">
|
||||
<option value="">Выберите сохранённый набор…</option>
|
||||
</select>
|
||||
<small>Сохраняются только в этом браузере.</small>
|
||||
</div>
|
||||
<div class="context-field">
|
||||
<label for="session-id">Идентификатор сессии <span>обязательно</span></label>
|
||||
<div class="context-input-row"><input id="session-id" autocomplete="off" spellcheck="false" placeholder="Вставьте session-id"><button class="copy-context" data-copy-context="session-id" type="button" aria-label="Скопировать идентификатор сессии" title="Скопировать">⧉</button></div>
|
||||
</div>
|
||||
<div class="context-field">
|
||||
<label for="workspace-id">Рабочая область <span>обязательно</span></label>
|
||||
<div class="context-input-row"><input id="workspace-id" autocomplete="off" spellcheck="false" placeholder="Вставьте workspace-id"><button class="copy-context" data-copy-context="workspace-id" type="button" aria-label="Скопировать рабочую область" title="Скопировать">⧉</button></div>
|
||||
</div>
|
||||
<p class="context-hint">Для «Состояния» нужны только эти два значения. Задача, ожидание и возможность находятся ниже в расширенных полях.</p>
|
||||
<details class="advanced-context">
|
||||
<summary>Расширенные идентификаторы</summary>
|
||||
<div>
|
||||
<label for="task-id">Задача</label>
|
||||
<input id="task-id" autocomplete="off" placeholder="task-id">
|
||||
<label for="prospective-id">Выбранное ожидание</label>
|
||||
<input id="prospective-id" autocomplete="off" placeholder="prospective-item-id">
|
||||
<label for="capability-id">Возможность</label>
|
||||
<input id="capability-id" autocomplete="off" value="cognitive-proposal">
|
||||
</div>
|
||||
</details>
|
||||
<div class="context-actions"><button class="secondary-button" type="submit">Загрузить контекст</button><button id="reset-context" class="text-button" type="button">Сбросить</button></div>
|
||||
<label for="session-id">Идентификатор сессии</label>
|
||||
<input id="session-id" autocomplete="off" placeholder="session-id">
|
||||
<label for="workspace-id">Рабочая область</label>
|
||||
<input id="workspace-id" autocomplete="off" placeholder="workspace-id">
|
||||
<label for="task-id">Задача</label>
|
||||
<input id="task-id" autocomplete="off" placeholder="task-id">
|
||||
<label for="prospective-id">Выбранное ожидание</label>
|
||||
<input id="prospective-id" autocomplete="off" placeholder="prospective-item-id">
|
||||
<label for="capability-id">Возможность</label>
|
||||
<input id="capability-id" autocomplete="off" value="cognitive-proposal">
|
||||
<button class="secondary-button" type="submit">Загрузить контекст</button>
|
||||
</form>
|
||||
<nav class="section-nav" aria-label="Навигация по кабинету">
|
||||
<a class="section-nav-link active" aria-current="location" href="#overview"><span>01</span><b>Состояние</b><small>фокус и сигналы</small></a>
|
||||
<a class="section-nav-link" href="#diagnostics"><span>02</span><b>Проверки</b><small>доступность и границы</small></a>
|
||||
<a class="section-nav-link" href="#operations"><span>03</span><b>Операции</b><small>ожидания и действия</small></a>
|
||||
<a class="section-nav-link" href="#learning"><span>04</span><b>Опыт</b><small>reflection и навыки</small></a>
|
||||
<a class="section-nav-link" href="#settings"><span>05</span><b>Настройки</b><small>интерфейс и обновление</small></a>
|
||||
</nav>
|
||||
<div class="session-stamp">
|
||||
<span>ACTOR</span>
|
||||
<strong id="session-actor">—</strong>
|
||||
@@ -113,7 +77,7 @@
|
||||
</aside>
|
||||
|
||||
<main class="workspace">
|
||||
<section id="overview" class="workspace-head" data-page="overview">
|
||||
<section class="workspace-head">
|
||||
<div>
|
||||
<p class="eyebrow">операционная картина</p>
|
||||
<h1>Состояние сейчас</h1>
|
||||
@@ -121,28 +85,14 @@
|
||||
<p id="last-update" class="last-update">Данные ещё не запрошены</p>
|
||||
</section>
|
||||
|
||||
<section class="signal-strip" aria-label="Сводка состояния" data-page="overview">
|
||||
<section class="signal-strip" aria-label="Сводка состояния">
|
||||
<div><span>ФОКУС</span><strong id="focus-signal" data-state="idle">ожидает</strong></div>
|
||||
<div><span>ЗАДАЧА</span><strong id="task-signal" data-state="idle">ожидает</strong></div>
|
||||
<div><span>CONTINUITY</span><strong id="prospective-signal" data-state="idle">ожидает</strong></div>
|
||||
<div><span>ВОЗМОЖНОСТЬ</span><strong id="capability-signal" data-state="idle">ожидает</strong></div>
|
||||
</section>
|
||||
|
||||
<section id="diagnostics" class="diagnostics-panel" aria-labelledby="diagnostics-title" data-page="diagnostics">
|
||||
<header class="panel-heading">
|
||||
<div><p>Проверяемая система</p><h2 id="diagnostics-title">Диагностика кабинета</h2></div>
|
||||
<div class="panel-heading-tools"><span id="diagnostic-summary">Проверка ещё не запускалась</span><button id="run-diagnostics" class="secondary-button compact-button" type="button">Проверить всё</button></div>
|
||||
</header>
|
||||
<div class="diagnostic-intro">
|
||||
<span class="diagnostic-pulse" aria-hidden="true"></span>
|
||||
<p>Здесь видны только ответы текущей авторизованной сессии. Проверка не меняет данные и помогает отличить отсутствие контекста от недоступности сервиса.</p>
|
||||
</div>
|
||||
<div id="diagnostic-list" class="diagnostic-list" aria-live="polite" aria-busy="false">
|
||||
<p class="register-empty">Нажмите «Проверить всё», чтобы получить актуальный снимок.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="advisor-panel" aria-labelledby="advisor-title" data-page="diagnostics">
|
||||
<section class="advisor-panel" aria-labelledby="advisor-title">
|
||||
<div class="advisor-intro">
|
||||
<p>COGNITIVE / ADVISORY V1</p>
|
||||
<h2 id="advisor-title">Спросить Домового</h2>
|
||||
@@ -170,7 +120,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="operations" class="register-panel" aria-labelledby="register-title" data-page="operations">
|
||||
<section class="register-panel" aria-labelledby="register-title">
|
||||
<header>
|
||||
<div><p>Continuity / ближайшие</p><h2 id="register-title">Реестр ожиданий</h2></div>
|
||||
<span id="prospective-count">0 ЗАПИСЕЙ</span>
|
||||
@@ -180,7 +130,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="activity-panel" aria-labelledby="activity-title" data-page="operations">
|
||||
<section class="activity-panel" aria-labelledby="activity-title">
|
||||
<header>
|
||||
<div><p>Commit / владелец</p><h2 id="activity-title">Журнал действий</h2></div>
|
||||
<div class="activity-tools">
|
||||
@@ -193,7 +143,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="learning" class="learning-panel" aria-labelledby="learning-title" data-page="learning">
|
||||
<section class="learning-panel" aria-labelledby="learning-title">
|
||||
<header>
|
||||
<div><p>Learning / evidence</p><h2 id="learning-title">Опыт и reflection</h2></div>
|
||||
<div class="activity-tools">
|
||||
@@ -217,20 +167,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="settings" class="settings-panel" aria-labelledby="settings-title" data-page="settings">
|
||||
<header class="panel-heading">
|
||||
<div><p>Локальные предпочтения</p><h2 id="settings-title">Настройки рабочего кабинета</h2></div>
|
||||
<span id="settings-saved" class="settings-saved">Сохраняются в этом браузере</span>
|
||||
</header>
|
||||
<form id="settings-form" class="settings-grid">
|
||||
<label class="setting-field"><span>Плотность данных</span><small>Выберите, сколько строк помещается на экране.</small><select id="setting-density"><option value="comfortable">Комфортная</option><option value="compact">Компактная</option></select></label>
|
||||
<label class="setting-field"><span>Автообновление</span><small>Повторная проверка состояния без изменения данных.</small><select id="setting-refresh"><option value="off">Выключено</option><option value="30">Каждые 30 секунд</option><option value="60">Каждую минуту</option><option value="300">Каждые 5 минут</option></select></label>
|
||||
<label class="setting-check"><input id="setting-motion" type="checkbox"><span><b>Сократить анимацию</b><small>Оставить спокойный режим для длительной работы.</small></span></label>
|
||||
<div class="settings-actions"><button id="save-settings" class="primary-button compact-button" type="submit"><span>Сохранить настройки</span><span aria-hidden="true">→</span></button><button id="reset-settings" class="text-button" type="button">Сбросить</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="data-grid" data-page="overview">
|
||||
<section class="data-grid">
|
||||
<article class="data-card focus-card">
|
||||
<header><span class="card-number">01</span><div><p>Situation</p><h2>Активный фокус</h2></div><span id="focus-badge" class="status-badge neutral">НЕ ЗАГРУЖЕН</span></header>
|
||||
<dl>
|
||||
@@ -251,7 +188,6 @@
|
||||
<div><dt>Ревизия</dt><dd id="task-revision">—</dd></div>
|
||||
</dl>
|
||||
<p id="task-message" class="card-message">Укажите задачу для проверки относительно активного фокуса.</p>
|
||||
<div class="id-actions"><button class="id-action" data-use-value="task-value" data-target-id="task-id" type="button">Использовать ID задачи</button><button class="id-action icon-id-action" data-copy-value="task-value" type="button" aria-label="Скопировать ID задачи" title="Скопировать ID">⧉</button></div>
|
||||
</article>
|
||||
|
||||
<article class="data-card prospective-card">
|
||||
@@ -269,7 +205,6 @@
|
||||
<div><dt>Ревизия</dt><dd id="prospective-revision">—</dd></div>
|
||||
</dl>
|
||||
<p id="prospective-message" class="card-message">Укажите идентификатор ProspectiveItem для проверки владельца и состояния.</p>
|
||||
<div class="id-actions"><button class="id-action" data-use-value="prospective-value" data-target-id="prospective-id" type="button">Использовать ID ожидания</button><button class="id-action icon-id-action" data-copy-value="prospective-value" type="button" aria-label="Скопировать ID ожидания" title="Скопировать ID">⧉</button></div>
|
||||
<section class="commit-zone" aria-labelledby="commit-zone-title">
|
||||
<div class="commit-zone-copy">
|
||||
<p>Commit / контролируемый переход</p>
|
||||
@@ -305,7 +240,6 @@
|
||||
<div><dt>Ревизия</dt><dd id="capability-revision">—</dd></div>
|
||||
<div><dt>Наблюдалось</dt><dd id="capability-observed">—</dd></div>
|
||||
</dl>
|
||||
<div class="id-actions"><button class="id-action" data-use-value="capability-value" data-target-id="capability-id" type="button">Использовать ID возможности</button><button class="id-action icon-id-action" data-copy-value="capability-value" type="button" aria-label="Скопировать ID возможности" title="Скопировать ID">⧉</button></div>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
@@ -352,6 +286,6 @@
|
||||
</dialog>
|
||||
|
||||
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
|
||||
<script src="/assets/console.js?v=20260821-context" defer></script>
|
||||
<script src="/assets/console.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000075-creator-chat-primary-surface.json",
|
||||
"task_id": "DEV-000075",
|
||||
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 6,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "1cfbb4ae66e24d514e59dabafb936c35546bd418",
|
||||
"risk_tier": "E",
|
||||
"intent": "Make the authenticated Creator Web cabinet understandable and operable for a human by adding section navigation, read-only diagnostics, and browser-local interface preferences without changing domain state or authority boundaries.",
|
||||
"requirement_ids": [
|
||||
"Human can move between overview, diagnostics, operations and settings",
|
||||
"Human can run all currently exposed read-only Creator checks from one diagnostic panel",
|
||||
"Interface preferences remain local to the browser and never change canonical state",
|
||||
"Existing explicit confirmation remains required for canonical mutations",
|
||||
"Desktop and mobile layouts remain usable"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["cmd/creator-web"],
|
||||
"paths": [
|
||||
"cmd/creator-web/static/index.html",
|
||||
"cmd/creator-web/static/assets/console.css",
|
||||
"cmd/creator-web/static/assets/console.js",
|
||||
"cmd/creator-web/server_test.go",
|
||||
"cmd/creator-web/chat_test.go",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000074-creator-cabinet-operations.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/**",
|
||||
"security/**",
|
||||
"database/**",
|
||||
"modules/**",
|
||||
"contracts/**",
|
||||
"adapters/**",
|
||||
"cmd/core-runtime/**",
|
||||
"cmd/commit-runtime/**",
|
||||
"cmd/continuity-runtime/**",
|
||||
"deploy/**"
|
||||
]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {
|
||||
"consume": ["existing authenticated Creator gateway endpoints"],
|
||||
"may_change": []
|
||||
},
|
||||
"semantic_owner": "none-interface-adapter",
|
||||
"canonical_state": [],
|
||||
"state_transitions": [
|
||||
"diagnostic request -> read-only gateway observation",
|
||||
"settings form submit -> browser-local preference update"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No new privilege or mutation endpoint",
|
||||
"No database migration",
|
||||
"Existing Commit actions retain explicit confirmation dialogs",
|
||||
"No deployment or neighboring project mutation"
|
||||
],
|
||||
"persistence_and_migrations": ["No canonical persistence; localStorage only for interface preferences"],
|
||||
"failure_retry_restart": ["Diagnostic failures are shown per check without exposing upstream error details"],
|
||||
"assumptions": [
|
||||
"Existing Creator API routes remain the source of truth",
|
||||
"Browser-local settings are disposable and do not affect domain behavior"
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Authenticated cabinet exposes navigation to all four human-facing sections",
|
||||
"read-only diagnostics checks session, advisory policy, capability, today, attention, actions and learning",
|
||||
"density, auto-refresh and reduced-motion preferences save and reset locally",
|
||||
"mobile layout remains readable",
|
||||
"existing Creator Web tests pass",
|
||||
"architecture and diff scope checks pass"
|
||||
],
|
||||
"required_tests": [
|
||||
"node --check cmd/creator-web/static/assets/console.js",
|
||||
"go test ./cmd/creator-web",
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
|
||||
"go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"git diff --check",
|
||||
"responsive browser inspection"
|
||||
],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "arch-generation-1",
|
||||
"base_repo_revision": "fd59b93463bea80459375ecb28864c03ec3efc42",
|
||||
"risk_tier": "E",
|
||||
"intent": "Consolidate Codex development memory and cold-start continuity so a fresh session can reconstruct active milestone, boundaries, tests, and next action from repository artifacts only. Do not change Domovoy product behavior; do not change deployment runtime configuration.",
|
||||
"requirement_ids": [
|
||||
"DEV-000061 governance continuity",
|
||||
"FOUNDATION_EXIT_AUDIT checkpoint continuity",
|
||||
"Cold-start reconstruction from repository only",
|
||||
"No product behavior regression",
|
||||
"Durable continuation fields"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [],
|
||||
"paths": [
|
||||
"AGENTS.md",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"docs/COLD_START_AND_DURABLE_CHECKPOINT.md",
|
||||
"docs/evidence/dev-000074-cold-start-reconstruction.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"architecture/architecture-registry.yaml",
|
||||
"architecture/architecture-registry.json",
|
||||
"architecture/adrs/**",
|
||||
"security/**",
|
||||
"modules/**",
|
||||
"contracts/**",
|
||||
"adapters/**",
|
||||
"cmd/**",
|
||||
"deploy/**",
|
||||
".gitea/**",
|
||||
"ci/**"
|
||||
]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {
|
||||
"consume": [],
|
||||
"may_change": []
|
||||
},
|
||||
"semantic_owner": "governance",
|
||||
"canonical_state": [
|
||||
"AGENTS.md contains the durable cold-start startup sequence and checkpoint rules.",
|
||||
"Checkpoint continuity is preserved in docs/PROJECT_STATUS plus reconstructed durable checkpoint fields."
|
||||
],
|
||||
"state_transitions": [
|
||||
"Fresh session -> read required artifacts -> reconstruct active task and phase",
|
||||
"Checkpoint update -> clear/record blockers -> define next action",
|
||||
"Preflight and checks -> produce final continuity status and evidence"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No direct external mutation",
|
||||
"No runtime/runtime-host configuration changes",
|
||||
"No production actions"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"No database migration",
|
||||
"No schema or privilege change",
|
||||
"No canonical ownership change"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"If required startup artifacts are missing, return BLOCKED_BY_MISSING_CAPABILITY and update checkpoint.",
|
||||
"If checkpoint and active task disagree, BLOCKED_BY_SPEC_GAP and require a corrective checkpoint update.",
|
||||
"Execution-window exhaustion is continuation state, not failure."
|
||||
],
|
||||
"assumptions": [
|
||||
"Foundation architecture and ADR set is already accepted.",
|
||||
"No production behavior change is authorized in this milestone."
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Fresh session reconstructs active DEV, phase, blockers, and next action from artifacts alone.",
|
||||
"ACTIVE_TASK points to DEV-000074 task packet after switching.",
|
||||
"AGENTS cold-start checklist remains practical and non-duplicative.",
|
||||
"Docs checkpoint explicitly records durable status with explicit evidence references.",
|
||||
"Architecture/diff checks pass.",
|
||||
"No changed files outside governance/documentation scope."
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT",
|
||||
"REQUIRES_MIGRATION_DECISION"
|
||||
]
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"task_id": "DEV-000075",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "39a27102c9665e41f29e85bc8f82a316bbbe3962",
|
||||
"risk_tier": "E",
|
||||
"intent": "Make live conversation with Domovoy the public starting surface and keep the Creator operator console on a separate admin route.",
|
||||
"requirement_ids": [
|
||||
"Public root opens the human chat",
|
||||
"Operator console remains available at a separate admin route",
|
||||
"Existing /chat route remains compatible",
|
||||
"Chat does not expose provider, model, binding, session or workspace internals"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["cmd/creator-web"],
|
||||
"paths": [
|
||||
"cmd/creator-web/server.go",
|
||||
"cmd/creator-web/server_test.go",
|
||||
"cmd/creator-web/chat_test.go",
|
||||
"cmd/creator-web/static/chat.html",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000075-creator-chat-primary-surface.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": ["architecture/**", "security/**", "database/**", "modules/**", "contracts/**", "adapters/**", "deploy/**"]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {"consume": ["existing /chat and authenticated Creator endpoints"], "may_change": []},
|
||||
"semantic_owner": "none-interface-adapter",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["GET / -> chat surface", "GET /admin -> operator console surface"],
|
||||
"side_effects_and_privileges": ["No new privilege", "No canonical mutation", "No model/provider routing change"],
|
||||
"persistence_and_migrations": ["No persistence or migration"],
|
||||
"failure_retry_restart": ["Existing chat failure behavior remains unchanged"],
|
||||
"assumptions": ["/chat is the existing human chat surface", "The operator console may be reached at /admin"],
|
||||
"acceptance_scenarios": ["Root serves chat", "Admin serves console", "Chat compatibility remains", "Internal IDs remain absent from chat HTML"],
|
||||
"required_tests": ["go test ./cmd/creator-web", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# Cold start reconstruction and durable checkpoint guidance
|
||||
|
||||
## 1) Fresh session reconstruction (no chat dependency)
|
||||
|
||||
Any new Codex session must reconstruct active state using repository artifacts only:
|
||||
|
||||
1. `git status`, `git rev-parse --abbrev-ref HEAD`, `git log -1 --oneline`
|
||||
2. `AGENTS.md`
|
||||
3. `architecture/ARCHITECTURE_CONSTITUTION.md`
|
||||
4. `architecture/CORE_SEMANTIC_MODEL_V1.md`
|
||||
5. `architecture/architecture-registry.yaml`
|
||||
6. `architecture/architecture-registry.json`
|
||||
7. `development/CODEX_DEVELOPMENT_CONTRACT.md`
|
||||
8. `dev/ACTIVE_TASK.json`
|
||||
9. active task packet referenced by `dev/ACTIVE_TASK.json`
|
||||
10. relevant accepted ADRs
|
||||
11. `docs/PROJECT_STATUS.md`
|
||||
12. relevant evidence artifacts in `docs/evidence/**` and tests configured for current task
|
||||
|
||||
The objective is to restore:
|
||||
|
||||
- what Domovoy is and current implementation state;
|
||||
- active DEV/task and scope;
|
||||
- architecture boundaries and governance stack;
|
||||
- last verified commit and next action.
|
||||
|
||||
No external chat context is required.
|
||||
|
||||
## 2) Canonical durable checkpoint fields
|
||||
|
||||
Current work checkpoints are represented through:
|
||||
|
||||
- `docs/PROJECT_STATUS.md` checkpoint blocks per DEV-id.
|
||||
- `dev/ACTIVE_TASK.json` for active packet identity.
|
||||
- active task packet fields and scope.
|
||||
|
||||
Required conceptual fields:
|
||||
|
||||
- `STATUS`
|
||||
- `CURRENT_PHASE`
|
||||
- `COMPLETED`
|
||||
- `IN_PROGRESS`
|
||||
- `NOT_STARTED`
|
||||
- `BLOCKERS`
|
||||
- `IMPORTANT_FINDINGS`
|
||||
- `EVIDENCE_OBTAINED`
|
||||
- `LAST_VERIFIED_COMMIT`
|
||||
- `NEXT_ACTION`
|
||||
|
||||
No single file must hold all facts; continuity is reconstructed from the three authoritative artifacts above plus repository evidence.
|
||||
|
||||
## 3) Practical continuity checks for a fresh session
|
||||
|
||||
The session is recoverable if it can, from the repository alone, answer:
|
||||
|
||||
- Which task is active and where to continue?
|
||||
- What phase is current (`NOT_STARTED` / `IN_PROGRESS` / `COMPLETED`)?
|
||||
- What blockers and assumptions are known?
|
||||
- What evidence was last verified and where is it stored?
|
||||
- What exact next action is safe to perform next?
|
||||
|
||||
Any ambiguous active state should be treated as blocked and converted into an explicit checkpoint update rather than implicit inference.
|
||||
|
||||
## 4) Deployment continuity rule
|
||||
|
||||
- Bootstrap config should remain minimal and task-locating.
|
||||
- Live canonical configuration belongs to canonical ownership and project artifacts, not ambient shell session state.
|
||||
- Missing runtime assumptions must fail early during startup checks, not defer silently to manual behavior.
|
||||
|
||||
## 5) Evidence expectations for this task family (DEV-000074)
|
||||
|
||||
- `docs/PROJECT_STATUS.md` checkpoint updated for DEV-000074.
|
||||
- `AGENTS.md` contains the startup and checkpoint governance.
|
||||
- `dev/ACTIVE_TASK.json` points to the active DEV-000074 packet.
|
||||
- Evidence artifact file or command output captured for cold-start reconstruction validation.
|
||||
|
||||
+15
-1
@@ -1,6 +1,20 @@
|
||||
# Assistant project status
|
||||
|
||||
Audit date: 2026-08-17. Active implementation: DEV-000072 revision 1.
|
||||
Audit date: 2026-08-18. Active implementation: DEV-000074.
|
||||
|
||||
## DEV-000074 checkpoint
|
||||
|
||||
- `DEV_000074_STATUS = COMPLETED`
|
||||
- `TASK_SCOPE = governance/cold-start continuity`
|
||||
- `CURRENT_PHASE = CONTEXT_AND_CHECKPOINT_CONSOLIDATION`
|
||||
- `COMPLETED = AGENTS.md startup/continuity sequence clarified; Cold start reconstruction guide added; task packet created; active task pointer updated; governance checks executed; cold-start reconstruction evidence captured`
|
||||
- `IN_PROGRESS = none`
|
||||
- `NOT_STARTED = product behavior, runtime configuration, or architecture migration changes`
|
||||
- `BLOCKERS = none`
|
||||
- `IMPORTANT_FINDINGS = chat-only state is insufficient for reconstruction; repository artifacts are now explicitly documented`
|
||||
- `EVIDENCE_OBTAINED = AGENTS.md, docs/COLD_START_AND_DURABLE_CHECKPOINT.md, dev/ACTIVE_TASK.json, dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json, check output`
|
||||
- `LAST_VERIFIED_COMMIT = 1c334fc6a4d9bd8c5b8f8f5a7ccebeccf7d98df2`
|
||||
- `NEXT_ACTION = none (awaiting next approved milestone)`
|
||||
|
||||
## DEV-000072 checkpoint
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# DEV-000074 cold-start reconstruction proof
|
||||
Date: 2026-08-18T00:19:35.0288130+03:00
|
||||
|
||||
1) Git context
|
||||
Branch: codex/dev-000072-semantic-deferral-boundary-v1
|
||||
HEAD: fd59b93463bea80459375ecb28864c03ec3efc42
|
||||
origin/main: 11a6a23f6e4f44dd88265a04846419a27f54df98
|
||||
MergeBase: ce666bbc43842d339e760957e3c59c490ae1c18a
|
||||
Status:
|
||||
M AGENTS.md
|
||||
M dev/ACTIVE_TASK.json
|
||||
M docs/PROJECT_STATUS.md
|
||||
?? dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json
|
||||
?? docs/COLD_START_AND_DURABLE_CHECKPOINT.md
|
||||
?? docs/evidence/dev-000074-cold-start-reconstruction.md
|
||||
|
||||
2) Active task pointer
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
|
||||
3) Active task file exists
|
||||
True
|
||||
|
||||
4) DEV-000074 packet
|
||||
{
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "arch-generation-1",
|
||||
"base_repo_revision": "fd59b93463bea80459375ecb28864c03ec3efc42",
|
||||
"risk_tier": "E",
|
||||
"intent": "Consolidate Codex development memory and cold-start continuity so a fresh session can reconstruct active milestone, boundaries, tests, and next action from repository artifacts only. Do not change Domovoy product behavior; do not change deployment runtime configuration.",
|
||||
"requirement_ids": [
|
||||
"DEV-000061 governance continuity",
|
||||
"FOUNDATION_EXIT_AUDIT checkpoint continuity",
|
||||
"Cold-start reconstruction from repository only",
|
||||
"No product behavior regression",
|
||||
"Durable continuation fields"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [],
|
||||
"paths": [
|
||||
"AGENTS.md",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"docs/COLD_START_AND_DURABLE_CHECKPOINT.md",
|
||||
"docs/evidence/dev-000074-cold-start-reconstruction.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"architecture/architecture-registry.yaml",
|
||||
"architecture/architecture-registry.json",
|
||||
"architecture/adrs/**",
|
||||
"security/**",
|
||||
"modules/**",
|
||||
"contracts/**",
|
||||
"adapters/**",
|
||||
"cmd/**",
|
||||
"deploy/**",
|
||||
".gitea/**",
|
||||
"ci/**"
|
||||
]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {
|
||||
"consume": [],
|
||||
"may_change": []
|
||||
},
|
||||
"semantic_owner": "governance",
|
||||
"canonical_state": [
|
||||
"AGENTS.md contains the durable cold-start startup sequence and checkpoint rules.",
|
||||
"Checkpoint continuity is preserved in docs/PROJECT_STATUS plus reconstructed durable checkpoint fields."
|
||||
],
|
||||
"state_transitions": [
|
||||
"Fresh session -> read required artifacts -> reconstruct active task and phase",
|
||||
"Checkpoint update -> clear/record blockers -> define next action",
|
||||
"Preflight and checks -> produce final continuity status and evidence"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No direct external mutation",
|
||||
"No runtime/runtime-host configuration changes",
|
||||
"No production actions"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"No database migration",
|
||||
"No schema or privilege change",
|
||||
"No canonical ownership change"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"If required startup artifacts are missing, return BLOCKED_BY_MISSING_CAPABILITY and update checkpoint.",
|
||||
"If checkpoint and active task disagree, BLOCKED_BY_SPEC_GAP and require a corrective checkpoint update.",
|
||||
"Execution-window exhaustion is continuation state, not failure."
|
||||
],
|
||||
"assumptions": [
|
||||
"Foundation architecture and ADR set is already accepted.",
|
||||
"No production behavior change is authorized in this milestone."
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Fresh session reconstructs active DEV, phase, blockers, and next action from artifacts alone.",
|
||||
"ACTIVE_TASK points to DEV-000074 task packet after switching.",
|
||||
"AGENTS cold-start checklist remains practical and non-duplicative.",
|
||||
"Docs checkpoint explicitly records durable status with explicit evidence references.",
|
||||
"Architecture/diff checks pass.",
|
||||
"No changed files outside governance/documentation scope."
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT",
|
||||
"REQUIRES_MIGRATION_DECISION"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
5) Required startup files
|
||||
AGENTS.md => True
|
||||
architecture/ARCHITECTURE_CONSTITUTION.md => True
|
||||
architecture/CORE_SEMANTIC_MODEL_V1.md => True
|
||||
architecture/architecture-registry.yaml => True
|
||||
architecture/architecture-registry.json => True
|
||||
development/CODEX_DEVELOPMENT_CONTRACT.md => True
|
||||
docs/PROJECT_STATUS.md => True
|
||||
docs/COLD_START_AND_DURABLE_CHECKPOINT.md => True
|
||||
|
||||
6) Required checks executed
|
||||
go test ./... => PASS
|
||||
go vet ./... => PASS
|
||||
go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json => PASS
|
||||
go run ./cmd/devctl check-architecture => PASS
|
||||
go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json => PASS
|
||||
go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json => PASS
|
||||
@@ -0,0 +1,166 @@
|
||||
[
|
||||
{
|
||||
"id": "A",
|
||||
"status": 200,
|
||||
"message": "ты кто",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "86fb129b40d2f2fbb4ab798d863bf35d",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?"
|
||||
},
|
||||
{
|
||||
"id": "B",
|
||||
"status": 200,
|
||||
"message": "сколько будет 2 + 2",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "570663950030add34f71cedb24f4256d",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "2 + 2 equals 4."
|
||||
},
|
||||
{
|
||||
"id": "C",
|
||||
"status": 200,
|
||||
"message": "как с утра",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "008192deff274c8ab7678c180ab7e04d",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день."
|
||||
},
|
||||
{
|
||||
"id": "D",
|
||||
"status": 200,
|
||||
"message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "4607460cd39d273d99ae50a7d8315517",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу"
|
||||
},
|
||||
{
|
||||
"id": "E",
|
||||
"message": "исправь прошлую формулировку",
|
||||
"status": 502,
|
||||
"body": "chat unavailable\n"
|
||||
},
|
||||
{
|
||||
"id": "F",
|
||||
"message": "обратись к модели gemma4 и спроси сколько звезд на небе",
|
||||
"status": 502,
|
||||
"body": "chat unavailable\n"
|
||||
},
|
||||
{
|
||||
"id": "G",
|
||||
"status": 200,
|
||||
"message": "просто напомни, что нужно проверить",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "89b69b8251258a689f38de98552cbed4",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут."
|
||||
},
|
||||
{
|
||||
"id": "H",
|
||||
"status": 200,
|
||||
"message": "Запомни тебя зовут Домовой, я тебя буду звать Домовым",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "0c3bcf9b8f423e93b1b255aa33a557db",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
},
|
||||
{
|
||||
"id": "H2",
|
||||
"status": 200,
|
||||
"message": "для меня ты Домовой",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "7847420f28c85894400c6a5be3e760d0",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
},
|
||||
{
|
||||
"id": "H3",
|
||||
"status": 200,
|
||||
"message": "давай я буду звать тебя Домовым",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "085e4194a61cbfd097118553342e063a",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,184 @@
|
||||
[
|
||||
{
|
||||
"id": "A",
|
||||
"status": 200,
|
||||
"message": "ты кто",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "7d14f020fc2134f0ba995c35ebe31523",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "B",
|
||||
"status": 200,
|
||||
"message": "сколько будет 2 + 2",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "0a739b4810420e0eb0615d8d4cd4b60b",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "2 + 2 equals 4.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "C",
|
||||
"status": 200,
|
||||
"message": "как с утра",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "4463c0cc31a552a23c0c0ba4eff06554",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "D",
|
||||
"status": 200,
|
||||
"message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "c80c1eb2ce08edca3add568825c444d7",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "EXPLICIT",
|
||||
"active_focus": "УПО"
|
||||
},
|
||||
{
|
||||
"id": "E",
|
||||
"status": 502,
|
||||
"message": "исправь прошлую формулировку",
|
||||
"error": "request_failed"
|
||||
},
|
||||
{
|
||||
"id": "F",
|
||||
"status": 502,
|
||||
"message": "обратись к модели gemma4 и спроси сколько звезд на небе",
|
||||
"error": "request_failed"
|
||||
},
|
||||
{
|
||||
"id": "G",
|
||||
"status": 200,
|
||||
"message": "просто напомни, что нужно проверить",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "0519382bed09ae5ea9c9a3b2f2af2641",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H",
|
||||
"status": 200,
|
||||
"message": "Запомни тебя зовут Домовой",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "aae512074bf81adb405c0c80331c9096",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H2",
|
||||
"status": 200,
|
||||
"message": "для меня ты Домовой",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "78a171bb2f405e72f98f3b19d7422514",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H3",
|
||||
"status": 200,
|
||||
"message": "давай я буду звать тебя Домовым",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "382bd2310ed4a86f671f02c45afe3aee",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H4",
|
||||
"status": 200,
|
||||
"message": "Вернемся к УПО",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "8cb57dcf2599947673d1c1a4bb154266",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Фокус переключён: УПО.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "EXPLICIT",
|
||||
"active_focus": "УПО"
|
||||
},
|
||||
{
|
||||
"id": "I",
|
||||
"status": 200,
|
||||
"message": "помоги ответить на письмо бухгалтеру",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "ebeacce8340779490936e702b67543fa",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения.",
|
||||
"work": false,
|
||||
"capability_status": "UNAVAILABLE",
|
||||
"capability_kind": "SEND_EMAIL",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
}
|
||||
]
|
||||
@@ -125,6 +125,10 @@ func BuildDeterministicCandidate(message string, corrects interaction.HumanUtter
|
||||
tokens := semanticTokens(message)
|
||||
hasQuestionMark := strings.Contains(message, "?")
|
||||
scores := semanticScores(tokens, hasQuestionMark, corrects != "")
|
||||
if isIdentityNamingPreferenceDirective(tokens) {
|
||||
scores[interaction.SpeechActDirective] += 5
|
||||
scores[interaction.SpeechActPreference] += 4
|
||||
}
|
||||
primary, primaryScore := topAct(scores)
|
||||
secondary, secondaryScore := secondAct(scores, primary)
|
||||
if (startsWithModalRequest(tokens) || startsWithExplanatoryRequest(tokens, "расска", "раска", "объяс", "опиш", "покаж", "подскаж")) &&
|
||||
@@ -170,6 +174,22 @@ func BuildDeterministicCandidate(message string, corrects interaction.HumanUtter
|
||||
return candidate
|
||||
}
|
||||
|
||||
func isIdentityNamingPreferenceDirective(tokens []string) bool {
|
||||
if !containsToken(tokens, "ты") && !containsToken(tokens, "тебя") && !containsToken(tokens, "тебе") && !containsToken(tokens, "вам") {
|
||||
return false
|
||||
}
|
||||
if containsStemInTokens(tokens, "запомни", "назови", "назвать", "назвав", "зови", "название", "называ", "обраща", "обращай", "обращайся", "звать", "зов", "зовут") {
|
||||
return true
|
||||
}
|
||||
if containsStemInTokens(tokens, "домов", "assist", "ассист", "assistant", "assistant") {
|
||||
return true
|
||||
}
|
||||
if containsStemInTokens(tokens, "буду", "будешь") && containsStemInTokens(tokens, "звать", "обращ", "называть") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func semanticTokens(message string) []string {
|
||||
lower := strings.ToLower(strings.TrimSpace(message))
|
||||
replacer := strings.NewReplacer(".", " ", ",", " ", "!", " ", "?", " ", ":", " ", ";", " ", "(", " ", ")", " ", "\"", " ", "'", " ", "«", " ", "»", " ", "+", " + ", "-", " - ", "/", " / ")
|
||||
@@ -369,6 +389,9 @@ func applicabilityFor(primary, secondary interaction.SpeechAct, primaryScore, se
|
||||
}
|
||||
|
||||
func needsCognitionForCandidate(primary, secondary interaction.SpeechAct, tokens []string) bool {
|
||||
if primary == interaction.SpeechActDirective && secondary == interaction.SpeechActPreference && isIdentityNamingPreferenceDirective(tokens) {
|
||||
return false
|
||||
}
|
||||
if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -93,6 +93,25 @@ func TestDeterministicCandidateTreatsModalExplanationRequestsAsCognitionWorthWit
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateClassifiesIdentityNamingPreferenceAsDirective(t *testing.T) {
|
||||
for _, message := range []string{
|
||||
"Запомни тебя зовут Домовой, я тебя буду звать Домовым",
|
||||
"я буду звать тебя Домовым",
|
||||
"давай я буду звать тебя Домовой",
|
||||
"теперь буду обращаться к тебе как к Домовой",
|
||||
"запомни, я называю тебя Домовым",
|
||||
"для меня ты Домовой",
|
||||
} {
|
||||
candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != interaction.SpeechActDirective {
|
||||
t.Fatalf("expected directive for %q, got %+v", message, candidate)
|
||||
}
|
||||
if candidate.NeedsCognition {
|
||||
t.Fatalf("expected no cognition for naming preference by default for %q, got %+v", message, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateTreatsModelRequestAsDirectiveNeedingCognition(t *testing.T) {
|
||||
candidate := BuildDeterministicCandidate("обратись к модели gemma4 и спроси сколько звезд на небе", "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != interaction.SpeechActDirective {
|
||||
|
||||
@@ -30,6 +30,10 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
|
||||
deterministicCandidate := BuildDeterministicCandidate(utterance.Content, utterance.CorrectsUtteranceID, millis)
|
||||
deterministicCandidate.UtteranceID = utterance.ID
|
||||
selectedCandidate := deterministicCandidate
|
||||
assertionVerification := string(epistemics.VerificationNotApplicable)
|
||||
if selectedCandidate.PrimarySpeechAct == interaction.SpeechActAssertion {
|
||||
assertionVerification = string(epistemics.VerificationUnverified)
|
||||
}
|
||||
result := SemanticResult{
|
||||
Interpretation: interaction.UtteranceInterpretation{
|
||||
ID: interaction.InterpretationID(interpretationID),
|
||||
@@ -38,7 +42,7 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
|
||||
SecondarySpeechAct: selectedCandidate.SecondarySpeechAct,
|
||||
ConfidenceMillis: selectedCandidate.ConfidenceMillis,
|
||||
Source: string(selectedCandidate.Source),
|
||||
AssertionVerification: string(epistemics.VerificationUnverified),
|
||||
AssertionVerification: assertionVerification,
|
||||
RequestedOutcome: selectedCandidate.RequestedOutcome,
|
||||
Clarification: selectedCandidate.Clarification,
|
||||
NeedsCognition: selectedCandidate.NeedsCognition,
|
||||
|
||||
@@ -66,6 +66,16 @@ func TestInterpretUtteranceQuestionAboutCurrentWorkUsesFocus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceIdentityNamingPreferenceNotAssertion(t *testing.T) {
|
||||
result := interpretForTest(t, "Запомни тебя зовут Домовой, я тебя буду звать Домовым", nil, "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
|
||||
t.Fatalf("expected directive for naming preference, got %s", result.Interpretation.SpeechAct)
|
||||
}
|
||||
if result.Interpretation.AssertionVerification == "UNVERIFIED" {
|
||||
t.Fatalf("expected no assertion semantics path for naming preference, got %#v", result.Interpretation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceInheritedFocusForThere(t *testing.T) {
|
||||
result := interpretForTest(t, "Добавь туда новую настройку.", activeFocus("assistant", "Assistant"), "")
|
||||
if len(result.Projects) != 1 || result.Projects[0].ProjectID != "assistant" || result.Projects[0].Provenance != situation.ProjectResolutionInheritedFocus {
|
||||
|
||||
Reference in New Issue
Block a user