Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54de3f44d0 | ||
|
|
11a6a23f6e |
@@ -2,45 +2,16 @@
|
||||
|
||||
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. 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.
|
||||
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.
|
||||
|
||||
If documents conflict, authority order is:
|
||||
|
||||
@@ -101,32 +72,3 @@ 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.
|
||||
|
||||
@@ -1,76 +1,488 @@
|
||||
:root {
|
||||
--ink: #e9eee7;
|
||||
--muted: #93a096;
|
||||
--base: #0b100e;
|
||||
--panel: #101714;
|
||||
--line: #2a352f;
|
||||
--signal: #c9a85f;
|
||||
--bad: #de816f;
|
||||
--good: #78c6a3;
|
||||
--radius: 3px;
|
||||
--ink: #e7efe9;
|
||||
--muted: #96a49d;
|
||||
--faint: #5f6e66;
|
||||
--surface: #0f1512;
|
||||
--surface-alt: #151d18;
|
||||
--line: #27322c;
|
||||
--base: #090d0b;
|
||||
--good: #7cc7a4;
|
||||
--signal: #ceb27a;
|
||||
--bad: #d88d78;
|
||||
--radius-lg: 16px;
|
||||
--radius: 10px;
|
||||
--sans: "Bahnschrift", "Aptos Narrow", "Segoe UI", sans-serif;
|
||||
--serif: Georgia, "Times New Roman", serif;
|
||||
--mono: "Cascadia Mono", Consolas, monospace;
|
||||
--shadow: 0 24px 56px rgba(0, 0, 0, .22);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
[hidden] { display: none !important; }
|
||||
body { margin: 0; min-width: 320px; min-height: 100vh; color: var(--ink); background: var(--base); font-family: var(--sans); }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
min-width: 320px;
|
||||
color: var(--ink);
|
||||
background:
|
||||
radial-gradient(circle at 10% 0%, rgba(120, 182, 160, .16), transparent 40%),
|
||||
radial-gradient(circle at 90% 0%, rgba(201, 168, 95, .16), transparent 35%),
|
||||
var(--base);
|
||||
font-family: var(--sans);
|
||||
}
|
||||
|
||||
button, input, textarea { font: inherit; }
|
||||
button { cursor: pointer; color: inherit; border-radius: var(--radius); }
|
||||
.chat-shell { min-height: 100vh; display: grid; }
|
||||
.login-panel { width: min(100% - 32px, 420px); margin: auto; padding: 46px 40px; border: 1px solid var(--line); background: var(--panel); }
|
||||
.brand-mark { width: 46px; height: 46px; display: grid; place-items: center; color: var(--base); background: var(--signal); font: 700 24px/1 var(--serif); clip-path: polygon(50% 0, 92% 22%, 92% 76%, 50% 100%, 8% 76%, 8% 22%); }
|
||||
h1 { margin: 30px 0; font: 400 58px/.95 var(--serif); letter-spacing: -.02em; }
|
||||
label { display: block; margin: 16px 0 8px; color: var(--muted); font: 600 10px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; }
|
||||
input, textarea { width: 100%; border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); background: #0b110e; }
|
||||
input { height: 48px; padding: 0 12px; }
|
||||
textarea { resize: vertical; min-height: 84px; max-height: 240px; padding: 14px; line-height: 1.45; }
|
||||
.login-form button, .composer button { border: 0; color: #11150f; background: var(--signal); font-weight: 700; }
|
||||
.login-form button { width: 100%; height: 50px; margin-top: 18px; }
|
||||
#login-error { min-height: 20px; color: var(--bad); font-size: 13px; }
|
||||
.chat-view { min-height: 100vh; display: grid; grid-template-rows: 64px minmax(0, 1fr); }
|
||||
.chat-header { display: flex; align-items: center; gap: 18px; padding: 0 22px; border-bottom: 1px solid var(--line); background: rgba(16, 23, 20, .96); }
|
||||
.chat-header a { color: var(--ink); text-decoration: none; font: 400 24px/1 var(--serif); }
|
||||
.chat-header span { color: var(--muted); font: 10px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
|
||||
#active-focus { margin-left: auto; color: var(--good); }
|
||||
.chat-header button { width: 38px; height: 38px; border: 1px solid var(--line); background: transparent; }
|
||||
.human-nav { display: flex; align-items: center; gap: 8px; }
|
||||
.nav-tab, .operator-link { height: 34px; padding: 0 12px; display: inline-flex; align-items: center; border: 1px solid var(--line); color: var(--muted); background: #0b110e; text-decoration: none; font: 11px/1 var(--mono); text-transform: uppercase; letter-spacing: .08em; }
|
||||
.nav-tab.active { color: #11150f; border-color: var(--signal); background: var(--signal); }
|
||||
.operator-link { color: var(--good); }
|
||||
.surface-panel { min-height: 0; position: relative; }
|
||||
.active-panel { display: grid; min-height: 0; }
|
||||
.messages { width: min(100%, 920px); margin: 0 auto; padding: 28px 18px 120px; display: flex; flex-direction: column; gap: 14px; overflow: auto; }
|
||||
.message { max-width: min(720px, 92%); padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius); line-height: 1.45; white-space: pre-wrap; }
|
||||
.message.user { align-self: flex-end; background: #151e1a; }
|
||||
.message.assistant { align-self: flex-start; background: #0f1613; }
|
||||
.message small { display: block; margin-top: 8px; color: var(--muted); font: 10px/1 var(--mono); text-transform: uppercase; }
|
||||
.composer { position: sticky; bottom: 0; width: min(100%, 920px); margin: 0 auto; padding: 16px 18px 20px; display: grid; grid-template-columns: minmax(0, 1fr) 130px; gap: 12px; background: linear-gradient(transparent, var(--base) 20%); }
|
||||
.composer button { height: 48px; align-self: end; }
|
||||
.composer button:disabled { cursor: wait; filter: saturate(.3); }
|
||||
.attention-surface, .history-surface { width: min(100%, 920px); margin: 0 auto; padding: 34px 18px; align-content: start; gap: 18px; }
|
||||
.surface-heading { display: flex; align-items: end; justify-content: space-between; gap: 16px; border-bottom: 1px solid var(--line); padding-bottom: 16px; }
|
||||
.surface-heading p { margin: 0; font: 400 36px/.95 var(--serif); }
|
||||
.surface-heading span, .attention-card small, .history-item span { color: var(--muted); font: 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .1em; }
|
||||
.surface-heading button { height: 36px; padding: 0 12px; border: 1px solid var(--line); background: transparent; }
|
||||
.attention-list, .history-list { display: grid; gap: 12px; }
|
||||
.attention-card, .history-item { border: 1px solid var(--line); background: #0f1613; padding: 16px; }
|
||||
.attention-card time { color: var(--signal); font: 12px/1 var(--mono); }
|
||||
.attention-card h2 { margin: 10px 0 8px; font: 400 24px/1.1 var(--serif); }
|
||||
.attention-card p, .history-item p { margin: 0 0 10px; color: var(--ink); line-height: 1.45; }
|
||||
.attention-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
|
||||
.attention-actions button { height: 34px; padding: 0 12px; border: 1px solid var(--line); background: #151e1a; color: var(--ink); font: 11px/1 var(--mono); text-transform: uppercase; letter-spacing: .08em; }
|
||||
.attention-actions button:first-child { border-color: var(--good); color: var(--good); }
|
||||
.history-item.user { border-color: #34443c; background: #151e1a; }
|
||||
.history-item strong { display: block; margin-bottom: 8px; color: var(--signal); }
|
||||
.empty-state { margin: 24px 0 0; color: var(--muted); }
|
||||
button { color: inherit; border: 0; border-radius: var(--radius); }
|
||||
p { margin: 0; }
|
||||
|
||||
.assistant-shell,
|
||||
.chat-view { min-height: 100vh; }
|
||||
|
||||
.auth-view {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 36px 20px;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: min(100%, 440px);
|
||||
padding: 30px;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(15, 21, 18, .92);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.brand-block,
|
||||
.header-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
color: var(--base);
|
||||
background: linear-gradient(145deg, var(--good), #7aa7bd);
|
||||
font: 700 24px/1 var(--serif);
|
||||
box-shadow: 0 0 0 4px rgba(124, 199, 164, .12);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font: 500 42px/1.05 var(--serif);
|
||||
}
|
||||
|
||||
.brand-kicker {
|
||||
margin: 0 0 4px;
|
||||
color: var(--faint);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
font-size: 11px;
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.auth-copy {
|
||||
margin: 18px 0 22px;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin: 0 0 6px;
|
||||
color: var(--faint);
|
||||
font-size: 12px;
|
||||
letter-spacing: .03em;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
color: var(--ink);
|
||||
background: #080e0c;
|
||||
}
|
||||
|
||||
input { height: 46px; padding: 0 14px; }
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
.primary-button:focus,
|
||||
.ghost-button:focus,
|
||||
.nav-tab:focus-visible,
|
||||
button:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 2px solid var(--signal);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 96px;
|
||||
max-height: 220px;
|
||||
padding: 14px;
|
||||
line-height: 1.45;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.input-with-action {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.icon-button,
|
||||
.ghost-button,
|
||||
.primary-button {
|
||||
height: 44px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.icon-button,
|
||||
.ghost-button {
|
||||
background: rgba(255, 255, 255, .05);
|
||||
border: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: var(--signal);
|
||||
color: #111810;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#login-button,
|
||||
.primary-button { width: 100%; }
|
||||
|
||||
#login-button,
|
||||
#send { margin-top: 14px; }
|
||||
|
||||
.login-form,
|
||||
.composer {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
#login-error {
|
||||
min-height: 20px;
|
||||
color: var(--bad);
|
||||
}
|
||||
|
||||
.chat-view {
|
||||
width: min(100%, 980px);
|
||||
margin: 0 auto;
|
||||
padding: 14px 16px 24px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.assistant-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.assistant-header h1 {
|
||||
font-size: clamp(30px, 4.4vw, 38px);
|
||||
letter-spacing: -.02em;
|
||||
}
|
||||
|
||||
.session-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
padding: 9px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
color: var(--muted);
|
||||
background: #0f1512;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.human-nav {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
padding: 6px;
|
||||
width: fit-content;
|
||||
background: #0c1210;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.nav-tab.active,
|
||||
.nav-tab:hover {
|
||||
background: var(--signal);
|
||||
color: #11150f;
|
||||
}
|
||||
|
||||
.surface-panel {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
min-height: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: linear-gradient(180deg, rgba(21, 29, 24, .86), rgba(15, 21, 18, .95));
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.surface-panel.active-panel {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.panel-toolbar {
|
||||
padding: 16px 18px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: #0c1210;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.panel-toolbar h2 {
|
||||
margin: 0;
|
||||
font: 500 26px/1.12 var(--serif);
|
||||
}
|
||||
|
||||
.panel-toolbar p {
|
||||
margin-top: 6px;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.focus-chip {
|
||||
margin: 8px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--good);
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(124, 199, 164, .45);
|
||||
border-radius: 999px;
|
||||
background: rgba(124, 199, 164, .12);
|
||||
}
|
||||
|
||||
.messages {
|
||||
padding: 24px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow: auto;
|
||||
min-height: calc(100vh - 280px);
|
||||
max-height: calc(100vh - 280px);
|
||||
}
|
||||
|
||||
.message {
|
||||
width: min(92%, 760px);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
line-height: 1.45;
|
||||
white-space: pre-wrap;
|
||||
background: #0f1512;
|
||||
}
|
||||
|
||||
.message.assistant { align-self: flex-start; background: #121a16; }
|
||||
.message.user { align-self: flex-end; background: #19231c; border-color: rgba(120, 191, 162, .4); }
|
||||
|
||||
.message-author { color: var(--faint); font-size: 11px; margin-bottom: 6px; font-weight: 700; }
|
||||
.message.user .message-author { color: #b6ddb8; }
|
||||
.message-time { margin-top: 8px; font-size: 11px; color: var(--faint); font-family: var(--mono); }
|
||||
.message small { display: block; margin-top: 6px; color: var(--faint); }
|
||||
|
||||
.composer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 14px 16px 16px;
|
||||
border-top: 1px solid var(--line);
|
||||
background: linear-gradient(transparent, rgba(11, 16, 14, .88));
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.composer-actions { display: grid; align-items: end; }
|
||||
|
||||
.composer button:disabled {
|
||||
cursor: wait;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.surface-panel:not(.active-panel) { display: none; }
|
||||
|
||||
.attention-surface,
|
||||
.history-surface { padding: 0 0 12px; }
|
||||
|
||||
.attention-list,
|
||||
.history-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.attention-card,
|
||||
.history-item {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #0b110f;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.attention-card h3,
|
||||
.history-item strong { margin: 0 0 8px; font: 600 15px/1.2 var(--serif); }
|
||||
|
||||
.attention-card time,
|
||||
.history-item time,
|
||||
.attention-card .muted,
|
||||
.history-item .muted {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.attention-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
|
||||
|
||||
.attention-actions .ghost-button,
|
||||
.action-row button {
|
||||
height: 34px;
|
||||
padding: 0 11px;
|
||||
background: rgba(255, 255, 255, .06);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.attention-actions .ghost-button {
|
||||
border: 1px solid rgba(194, 168, 118, .48);
|
||||
color: #f2e8d0;
|
||||
}
|
||||
|
||||
.attention-actions .ghost-button.primary { border-color: transparent; background: var(--good); color: #11211b; }
|
||||
|
||||
.history-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.history-item p {
|
||||
margin: 0 0 8px;
|
||||
color: var(--ink);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.history-item .history-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
color: var(--faint);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.history-item .message-kind {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
color: var(--signal);
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
margin: 8px auto 0;
|
||||
max-width: 980px;
|
||||
color: var(--faint);
|
||||
padding: 10px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(15, 21, 18, .8);
|
||||
min-height: 18px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.status-bar.visible { visibility: visible; }
|
||||
|
||||
.composer-error {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
max-width: min(420px, calc(100vw - 32px));
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(216, 141, 120, .36);
|
||||
background: #241715;
|
||||
color: #ffd8ca;
|
||||
padding: 10px 12px;
|
||||
transform: translateY(16px);
|
||||
opacity: 0;
|
||||
transition: .2s ease;
|
||||
}
|
||||
|
||||
.composer-error.show {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
clip-path: inset(50%);
|
||||
border: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: var(--muted);
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.empty-state.error { color: var(--bad); }
|
||||
@media (max-width: 640px) {
|
||||
.composer { grid-template-columns: 1fr; }
|
||||
.composer button { width: 100%; }
|
||||
.chat-header { gap: 10px; padding: 0 12px; }
|
||||
.chat-header span { display: none; }
|
||||
.human-nav { overflow-x: auto; }
|
||||
.operator-link, .nav-tab { padding: 0 9px; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.chat-view {
|
||||
padding: 10px 10px 18px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.assistant-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.session-state { width: 100%; justify-content: space-between; }
|
||||
|
||||
.messages {
|
||||
min-height: calc(100vh - 320px);
|
||||
max-height: calc(100vh - 320px);
|
||||
}
|
||||
|
||||
.composer {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.panel-toolbar {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.attention-list,
|
||||
.history-list {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,227 +2,518 @@ const loginView = document.querySelector("#login-view");
|
||||
const chatView = document.querySelector("#chat-view");
|
||||
const loginForm = document.querySelector("#login-form");
|
||||
const loginError = document.querySelector("#login-error");
|
||||
const togglePassword = document.querySelector("#toggle-password");
|
||||
const usernameInput = document.querySelector("#username");
|
||||
const passwordInput = document.querySelector("#password");
|
||||
const messages = document.querySelector("#messages");
|
||||
const chatForm = document.querySelector("#chat-form");
|
||||
const messageInput = document.querySelector("#message");
|
||||
const sendButton = document.querySelector("#send");
|
||||
const logoutButton = document.querySelector("#logout");
|
||||
const clearHistoryButton = document.querySelector("#clear-history");
|
||||
const activeFocus = document.querySelector("#active-focus");
|
||||
const todayList = document.querySelector("#today-list");
|
||||
const todaySubtitle = document.querySelector("#today-subtitle");
|
||||
const refreshToday = document.querySelector("#refresh-today");
|
||||
const historyList = document.querySelector("#history-list");
|
||||
const navTabs = Array.from(document.querySelectorAll(".nav-tab"));
|
||||
const statusPill = document.querySelector("#connection-state");
|
||||
const statusBar = document.querySelector("#status-bar");
|
||||
const composerError = document.querySelector("#composer-error");
|
||||
|
||||
let token = sessionStorage.getItem("domovoy.chat.token") || "";
|
||||
let conversationID = sessionStorage.getItem("domovoy.chat.conversation") || "";
|
||||
const SESSION_KEY = "domovoy.chat.token";
|
||||
const CONVERSATION_KEY = "domovoy.chat.conversation";
|
||||
const SECTION_KEY = "domovoy.chat.section";
|
||||
|
||||
let token = sessionStorage.getItem(SESSION_KEY) || "";
|
||||
let conversationID = sessionStorage.getItem(CONVERSATION_KEY) || "";
|
||||
let localHistory = [];
|
||||
let todayRequestInFlight = false;
|
||||
|
||||
function setConnectionState(text, tone = "neutral") {
|
||||
if (!statusPill) return;
|
||||
statusPill.textContent = text;
|
||||
if (tone === "bad") {
|
||||
statusPill.style.borderColor = "rgba(216, 141, 120, .6)";
|
||||
statusPill.style.color = "var(--bad)";
|
||||
} else {
|
||||
statusPill.style.borderColor = "var(--line)";
|
||||
statusPill.style.color = "var(--muted)";
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
if (!statusBar) return;
|
||||
statusBar.textContent = message;
|
||||
statusBar.classList.add("visible");
|
||||
clearTimeout(showToast._timer);
|
||||
showToast._timer = setTimeout(() => {
|
||||
statusBar.classList.remove("visible");
|
||||
}, 2800);
|
||||
}
|
||||
|
||||
function showComposerError(message) {
|
||||
if (!composerError) return;
|
||||
composerError.textContent = message;
|
||||
composerError.classList.add("show");
|
||||
clearTimeout(showComposerError._timer);
|
||||
showComposerError._timer = setTimeout(() => {
|
||||
composerError.classList.remove("show");
|
||||
}, 2600);
|
||||
}
|
||||
|
||||
function formatTime(value = new Date()) {
|
||||
return new Intl.DateTimeFormat("ru-RU", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function showChat() {
|
||||
setConnectionState("Сессия активна");
|
||||
loginView.hidden = true;
|
||||
chatView.hidden = false;
|
||||
|
||||
if (!messages.children.length) {
|
||||
appendMessage("assistant", "Я рядом. Напишите обычное сообщение, без внутренних идентификаторов.");
|
||||
appendMessage("assistant", "Я рядом. Пишите просто и по-человечески, я всё пойму.");
|
||||
}
|
||||
|
||||
const storedSection = localStorage.getItem(SECTION_KEY);
|
||||
activateTab(storedSection === "today-panel" || storedSection === "history-panel" ? storedSection : "chat-panel");
|
||||
messageInput.focus();
|
||||
}
|
||||
|
||||
function showLogin() {
|
||||
setConnectionState("Вход не выполнен", "bad");
|
||||
chatView.hidden = true;
|
||||
loginView.hidden = false;
|
||||
usernameInput?.focus();
|
||||
}
|
||||
|
||||
function appendMessage(kind, text, meta = "") {
|
||||
const node = document.createElement("div");
|
||||
node.className = `message ${kind}`;
|
||||
node.textContent = text;
|
||||
if (meta) {
|
||||
const small = document.createElement("small");
|
||||
small.textContent = meta;
|
||||
node.appendChild(small);
|
||||
}
|
||||
messages.appendChild(node);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
localHistory.push({kind, text, meta, at: new Date()});
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function updateActiveFocus(value, provenance = "") {
|
||||
if (!activeFocus) return;
|
||||
activeFocus.textContent = value ? `Сейчас обсуждаем: ${value}` : "";
|
||||
activeFocus.hidden = !value;
|
||||
function endSession(message = "Сессия завершена") {
|
||||
token = "";
|
||||
conversationID = "";
|
||||
sessionStorage.removeItem(SESSION_KEY);
|
||||
sessionStorage.removeItem(CONVERSATION_KEY);
|
||||
messages.replaceChildren();
|
||||
localHistory = [];
|
||||
updateActiveFocus("");
|
||||
setConnectionState("Вход не выполнен", "bad");
|
||||
showToast(message);
|
||||
showLogin();
|
||||
}
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const headers = {"Content-Type": "application/json", ...(options.headers || {})};
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
const response = await fetch(path, {...options, headers});
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
const headers = new Headers(options.headers || {});
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
if (!headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
const response = await fetch(path, { ...options, headers });
|
||||
|
||||
if (response.status === 401 && path !== "/api/session") {
|
||||
endSession("Сессия просрочена, войдите снова");
|
||||
throw new Error("Требуется авторизация");
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = (await response.text()).trim();
|
||||
throw new Error(detail || `Ошибка ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
showComposerError(message || "Произошла ошибка");
|
||||
}
|
||||
|
||||
function appendMessage(kind, text, meta = "") {
|
||||
const node = document.createElement("article");
|
||||
node.className = `message ${kind}`;
|
||||
|
||||
const who = document.createElement("strong");
|
||||
who.className = "message-author";
|
||||
who.textContent = kind === "user" ? "Ты" : "Домовой";
|
||||
|
||||
const body = document.createElement("p");
|
||||
body.textContent = text;
|
||||
|
||||
node.append(who, body);
|
||||
|
||||
if (meta) {
|
||||
const metaNode = document.createElement("small");
|
||||
metaNode.textContent = meta;
|
||||
node.appendChild(metaNode);
|
||||
}
|
||||
|
||||
const when = document.createElement("time");
|
||||
when.className = "message-time";
|
||||
when.textContent = formatTime();
|
||||
node.appendChild(when);
|
||||
|
||||
messages.appendChild(node);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
|
||||
localHistory.push({
|
||||
kind,
|
||||
text,
|
||||
meta,
|
||||
at: new Date(),
|
||||
});
|
||||
|
||||
if (localHistory.length > 120) {
|
||||
localHistory = localHistory.slice(-120);
|
||||
}
|
||||
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
function updateActiveFocus(value) {
|
||||
if (!activeFocus) return;
|
||||
if (value) {
|
||||
activeFocus.textContent = `Сейчас обсуждаем: ${value}`;
|
||||
activeFocus.hidden = false;
|
||||
} else {
|
||||
activeFocus.hidden = true;
|
||||
activeFocus.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
function humanMeta(result) {
|
||||
if (result.reminder_scheduled) return "Напоминание сохранено. Я вернусь к нему сам.";
|
||||
if (result.delegation_id && result.delegation_state) return `Передал в coding-agent: ${humanDelegationState(result.delegation_state)}.`;
|
||||
if (result.capability_status && result.capability_status !== "AVAILABLE") return "Возможность пока не подключена.";
|
||||
if (result.work_planned) return "Я разложил это как работу.";
|
||||
if (result.reminder_scheduled) {
|
||||
return "Я зафиксировал напоминание и сообщу, когда наступит время.";
|
||||
}
|
||||
if (result.delegation_id && result.delegation_state) {
|
||||
return `Передал дальше: ${humanDelegationState(result.delegation_state)}.`;
|
||||
}
|
||||
if (result.capability_status && result.capability_status !== "AVAILABLE") {
|
||||
return "Эта задача сейчас проверяется отдельно.";
|
||||
}
|
||||
if (result.work_planned) {
|
||||
return "Я взял задачу в работу и отслежу статус.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function humanDelegationState(state) {
|
||||
switch (state) {
|
||||
case "RUNNING":
|
||||
return "работа запущена";
|
||||
return "выполняется";
|
||||
case "WAITING":
|
||||
return "ожидаю следующий шаг";
|
||||
return "ожидает следующего шага";
|
||||
case "BLOCKED":
|
||||
return "нужно решение человека";
|
||||
return "нужен человеческий выбор";
|
||||
case "VERIFYING":
|
||||
return "результат проверяется";
|
||||
return "результат на проверке";
|
||||
case "COMPLETED":
|
||||
return "работа завершена";
|
||||
return "готово";
|
||||
case "FAILED":
|
||||
return "run завершился ошибкой";
|
||||
return "завершилось ошибкой";
|
||||
default:
|
||||
return state.toLowerCase();
|
||||
return String(state || "обрабатывается").toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function renderHistory() {
|
||||
if (!historyList) return;
|
||||
|
||||
historyList.replaceChildren();
|
||||
const items = localHistory.slice(-30).reverse();
|
||||
const items = localHistory.slice(-50).slice().reverse();
|
||||
|
||||
if (!items.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "empty-state";
|
||||
empty.textContent = "История появится после сообщений в этом окне.";
|
||||
empty.textContent = "История появится после первых сообщений в этом окне.";
|
||||
historyList.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
for (const item of items) {
|
||||
|
||||
items.forEach((item) => {
|
||||
const row = document.createElement("article");
|
||||
row.className = `history-item ${item.kind}`;
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = item.kind === "user" ? "Вы" : "Домовой";
|
||||
row.className = "history-item";
|
||||
|
||||
const who = document.createElement("strong");
|
||||
who.className = "message-kind";
|
||||
who.textContent = item.kind === "user" ? "Ты" : "Домовой";
|
||||
|
||||
const body = document.createElement("p");
|
||||
body.textContent = item.text;
|
||||
const time = document.createElement("span");
|
||||
time.textContent = item.at.toLocaleTimeString("ru-RU", {hour: "2-digit", minute: "2-digit"});
|
||||
row.append(title, body, time);
|
||||
|
||||
const footer = document.createElement("div");
|
||||
footer.className = "history-meta";
|
||||
const time = document.createElement("time");
|
||||
time.textContent = formatTime(item.at);
|
||||
const meta = document.createElement("span");
|
||||
meta.textContent = item.meta || "";
|
||||
footer.append(time, meta);
|
||||
|
||||
row.append(who, body, footer);
|
||||
historyList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function createTodayCard(item) {
|
||||
const card = document.createElement("article");
|
||||
card.className = "attention-card";
|
||||
|
||||
const title = document.createElement("h3");
|
||||
const when = document.createElement("time");
|
||||
const state = document.createElement("p");
|
||||
const source = document.createElement("p");
|
||||
const hint = document.createElement("p");
|
||||
const actions = document.createElement("div");
|
||||
|
||||
const stamp = item.WhenUnixMillis || item.when_unix_millis || 0;
|
||||
when.textContent = stamp
|
||||
? new Date(stamp).toLocaleString("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "текущий момент";
|
||||
when.className = "muted";
|
||||
|
||||
title.textContent = item.Title || item.title || "Событие";
|
||||
state.textContent = item.HumanState || item.human_state || "ожидает внимания";
|
||||
state.className = "muted";
|
||||
|
||||
const sourceText = [
|
||||
item.SourceDescription || item.source_description,
|
||||
item.DeliveryState || item.delivery_state,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
source.textContent = sourceText || "Источник не задан";
|
||||
source.className = "muted";
|
||||
|
||||
const hintText = item.ResponseHint || item.response_hint || "";
|
||||
if (hintText) {
|
||||
hint.textContent = hintText;
|
||||
}
|
||||
|
||||
actions.className = "attention-actions";
|
||||
|
||||
const quick = [
|
||||
{ label: "Готово", text: "Готово", tone: "primary" },
|
||||
{ label: "Позже", text: "Отложи на 10 минут" },
|
||||
{ label: "Отменить", text: "Отмени" },
|
||||
];
|
||||
|
||||
quick.forEach((action) => {
|
||||
const actionButton = document.createElement("button");
|
||||
actionButton.type = "button";
|
||||
actionButton.className = `ghost-button ${action.tone || ""}`.trim();
|
||||
actionButton.textContent = action.label;
|
||||
|
||||
actionButton.addEventListener("click", async () => {
|
||||
const previous = actionButton.textContent;
|
||||
actionButton.disabled = true;
|
||||
actionButton.textContent = "Отправляю…";
|
||||
try {
|
||||
await sendChatMessage(action.text, { silentUser: false });
|
||||
await loadToday();
|
||||
activateTab("chat-panel");
|
||||
} finally {
|
||||
actionButton.disabled = false;
|
||||
actionButton.textContent = previous;
|
||||
}
|
||||
});
|
||||
|
||||
actions.appendChild(actionButton);
|
||||
});
|
||||
|
||||
card.append(title, when, state, source);
|
||||
if (hintText) card.append(hint);
|
||||
card.append(actions);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function renderTodayEmpty(message) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "empty-state";
|
||||
empty.textContent = message;
|
||||
todayList.appendChild(empty);
|
||||
}
|
||||
|
||||
async function loadToday() {
|
||||
if (!todayList || !token) return;
|
||||
if (!todayList || todayRequestInFlight) return;
|
||||
|
||||
todayRequestInFlight = true;
|
||||
todayList.replaceChildren();
|
||||
const loading = document.createElement("p");
|
||||
loading.className = "empty-state";
|
||||
loading.textContent = "Смотрю, что требует внимания...";
|
||||
todayList.appendChild(loading);
|
||||
renderTodayEmpty("Собираю актуальные напоминания…");
|
||||
|
||||
try {
|
||||
const result = await api("/api/today", {method: "GET"});
|
||||
todayList.replaceChildren();
|
||||
const result = await api("/api/today", { method: "GET" });
|
||||
const items = result.Items || result.items || [];
|
||||
|
||||
todayList.replaceChildren();
|
||||
|
||||
if (!items.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "empty-state";
|
||||
empty.textContent = "Сейчас ничего срочного не требует внимания.";
|
||||
todayList.appendChild(empty);
|
||||
todaySubtitle.textContent = "Сейчас спокойный период. Нечего тревожного";
|
||||
renderTodayEmpty("Сейчас ничего срочного не требует внимания.");
|
||||
return;
|
||||
}
|
||||
|
||||
todaySubtitle.textContent = `Найдено записей: ${items.length}`;
|
||||
|
||||
for (const item of items) {
|
||||
const card = document.createElement("article");
|
||||
card.className = "attention-card";
|
||||
const when = document.createElement("time");
|
||||
const stamp = item.WhenUnixMillis || item.when_unix_millis || 0;
|
||||
when.textContent = stamp ? new Date(stamp).toLocaleString("ru-RU", {day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit"}) : "сейчас";
|
||||
const title = document.createElement("h2");
|
||||
title.textContent = item.Title || item.title || "Событие Домового";
|
||||
const state = document.createElement("p");
|
||||
state.textContent = item.HumanState || item.human_state || "ожидает внимания";
|
||||
const source = document.createElement("small");
|
||||
source.textContent = [item.SourceDescription || item.source_description, item.DeliveryState || item.delivery_state].filter(Boolean).join(" · ");
|
||||
const hintText = item.ResponseHint || item.response_hint || "";
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "attention-actions";
|
||||
for (const [label, text] of [["Готово", "Готово"], ["Отложить", "Отложи на 10 минут"], ["Отменить", "Отмени"]]) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = label;
|
||||
button.addEventListener("click", async () => {
|
||||
await sendChatMessage(text, {silentUser: false});
|
||||
await loadToday();
|
||||
activateTab("chat-panel");
|
||||
});
|
||||
actions.appendChild(button);
|
||||
}
|
||||
if (hintText) {
|
||||
const hint = document.createElement("small");
|
||||
hint.textContent = hintText;
|
||||
card.append(when, title, state, source, hint, actions);
|
||||
} else {
|
||||
card.append(when, title, state, source, actions);
|
||||
}
|
||||
todayList.appendChild(card);
|
||||
todayList.appendChild(createTodayCard(item));
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
todayList.replaceChildren();
|
||||
const error = document.createElement("p");
|
||||
error.className = "empty-state error";
|
||||
error.textContent = "Внимание сейчас недоступно. Чат можно продолжать.";
|
||||
todayList.appendChild(error);
|
||||
const err = document.createElement("p");
|
||||
err.className = "empty-state error";
|
||||
err.textContent = error.message || "Сегодня недоступен на мгновение.";
|
||||
todayList.appendChild(err);
|
||||
todaySubtitle.textContent = "Не удалось обновить список.";
|
||||
} finally {
|
||||
todayRequestInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendChatMessage(text, options = {}) {
|
||||
const clean = text.trim();
|
||||
if (!clean) return;
|
||||
if (!options.silentUser) appendMessage("user", clean);
|
||||
|
||||
if (!options.silentUser) {
|
||||
appendMessage("user", clean);
|
||||
}
|
||||
|
||||
sendButton.disabled = true;
|
||||
messageInput.disabled = true;
|
||||
const originalLabel = sendButton.textContent;
|
||||
sendButton.textContent = "Отправляю…";
|
||||
|
||||
try {
|
||||
const result = await api("/api/chat/messages", {method: "POST", body: JSON.stringify({conversation_id: conversationID, message: clean})});
|
||||
const result = await api("/api/chat/messages", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
conversation_id: conversationID,
|
||||
message: clean,
|
||||
}),
|
||||
});
|
||||
|
||||
conversationID = result.conversation_id;
|
||||
sessionStorage.setItem("domovoy.chat.conversation", conversationID);
|
||||
updateActiveFocus(result.active_focus, result.project_provenance);
|
||||
appendMessage("assistant", result.reply_text || "Сообщение записано.", humanMeta(result));
|
||||
if (conversationID) {
|
||||
sessionStorage.setItem(CONVERSATION_KEY, conversationID);
|
||||
}
|
||||
|
||||
updateActiveFocus(result.active_focus);
|
||||
appendMessage("assistant", result.reply_text || "Принял тебя. Подготовлю ответ.", humanMeta(result));
|
||||
|
||||
if (/что сейчас требует моего внимания/i.test(clean)) {
|
||||
await loadToday();
|
||||
activateTab("today-panel");
|
||||
}
|
||||
} catch {
|
||||
appendMessage("assistant", "Чат сейчас недоступен. Сообщение не принято runtime.");
|
||||
|
||||
showToast("Сообщение отправлено");
|
||||
} catch (error) {
|
||||
appendMessage("assistant", "Я не могу отправить сообщение в этот момент. Проверьте сеть и авторизацию.");
|
||||
showError(error.message);
|
||||
} finally {
|
||||
sendButton.disabled = false;
|
||||
messageInput.focus();
|
||||
sendButton.textContent = originalLabel;
|
||||
messageInput.disabled = false;
|
||||
if (!options.silentUser) {
|
||||
messageInput.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function activateTab(panelID) {
|
||||
for (const tab of navTabs) tab.classList.toggle("active", tab.dataset.tab === panelID);
|
||||
for (const panel of document.querySelectorAll(".surface-panel")) {
|
||||
let activeSet = false;
|
||||
navTabs.forEach((tab) => {
|
||||
const isActive = tab.dataset.tab === panelID;
|
||||
tab.classList.toggle("active", isActive);
|
||||
tab.setAttribute("aria-selected", String(isActive));
|
||||
tab.tabIndex = isActive ? 0 : -1;
|
||||
if (isActive) activeSet = true;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".surface-panel").forEach((panel) => {
|
||||
const active = panel.id === panelID;
|
||||
panel.hidden = !active;
|
||||
panel.classList.toggle("active-panel", active);
|
||||
});
|
||||
|
||||
if (!activeSet) return;
|
||||
|
||||
localStorage.setItem(SECTION_KEY, panelID);
|
||||
|
||||
if (panelID === "today-panel") {
|
||||
loadToday();
|
||||
}
|
||||
|
||||
if (panelID === "history-panel") {
|
||||
renderHistory();
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTabs(event) {
|
||||
const current = navTabs.findIndex((tab) => tab.classList.contains("active"));
|
||||
if (current === -1) return;
|
||||
|
||||
if (event.key === "ArrowLeft") {
|
||||
event.preventDefault();
|
||||
const index = (current - 1 + navTabs.length) % navTabs.length;
|
||||
activateTab(navTabs[index].dataset.tab);
|
||||
navTabs[index].focus();
|
||||
}
|
||||
|
||||
if (event.key === "ArrowRight") {
|
||||
event.preventDefault();
|
||||
const index = (current + 1) % navTabs.length;
|
||||
activateTab(navTabs[index].dataset.tab);
|
||||
navTabs[index].focus();
|
||||
}
|
||||
if (panelID === "today-panel") loadToday();
|
||||
if (panelID === "history-panel") renderHistory();
|
||||
}
|
||||
|
||||
loginForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const username = usernameInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
const loginButton = document.querySelector("#login-button");
|
||||
|
||||
loginError.textContent = "";
|
||||
const username = document.querySelector("#username").value;
|
||||
const password = document.querySelector("#password").value;
|
||||
loginButton.disabled = true;
|
||||
loginButton.textContent = "Проверяю…";
|
||||
|
||||
try {
|
||||
const result = await api("/api/session", {method: "POST", body: JSON.stringify({username, password})});
|
||||
token = result.Token || result.token;
|
||||
sessionStorage.setItem("domovoy.chat.token", token);
|
||||
const result = await api("/api/session", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
token = result.Token || result.token || "";
|
||||
if (!token) {
|
||||
throw new Error("Пустой токен");
|
||||
}
|
||||
|
||||
sessionStorage.setItem(SESSION_KEY, token);
|
||||
conversationID = "";
|
||||
sessionStorage.removeItem(CONVERSATION_KEY);
|
||||
showChat();
|
||||
} catch {
|
||||
loginError.textContent = "Вход не выполнен";
|
||||
loginError.textContent = "Не удалось войти. Проверьте логин и пароль.";
|
||||
showToast("Не удалось войти");
|
||||
} finally {
|
||||
loginButton.disabled = false;
|
||||
loginButton.textContent = "Открыть чат";
|
||||
}
|
||||
});
|
||||
|
||||
togglePassword?.addEventListener("click", () => {
|
||||
const isPassword = passwordInput.type === "password";
|
||||
passwordInput.type = isPassword ? "text" : "password";
|
||||
togglePassword.textContent = isPassword ? "скрыть" : "показать";
|
||||
togglePassword.setAttribute("aria-label", isPassword ? "Скрыть пароль" : "Показать пароль");
|
||||
});
|
||||
|
||||
chatForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const text = messageInput.value.trim();
|
||||
@@ -231,20 +522,31 @@ chatForm.addEventListener("submit", async (event) => {
|
||||
await sendChatMessage(text);
|
||||
});
|
||||
|
||||
messageInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
chatForm.requestSubmit();
|
||||
}
|
||||
});
|
||||
|
||||
for (const tab of navTabs) {
|
||||
tab.addEventListener("click", () => activateTab(tab.dataset.tab));
|
||||
tab.addEventListener("click", () => {
|
||||
activateTab(tab.dataset.tab);
|
||||
});
|
||||
tab.addEventListener("keydown", navigateTabs);
|
||||
}
|
||||
|
||||
if (refreshToday) refreshToday.addEventListener("click", loadToday);
|
||||
refreshToday?.addEventListener("click", loadToday);
|
||||
|
||||
logoutButton.addEventListener("click", () => {
|
||||
token = "";
|
||||
conversationID = "";
|
||||
sessionStorage.removeItem("domovoy.chat.token");
|
||||
sessionStorage.removeItem("domovoy.chat.conversation");
|
||||
clearHistoryButton?.addEventListener("click", () => {
|
||||
localHistory = [];
|
||||
messages.replaceChildren();
|
||||
updateActiveFocus("");
|
||||
showLogin();
|
||||
renderHistory();
|
||||
showToast("История очищена");
|
||||
});
|
||||
|
||||
logoutButton?.addEventListener("click", () => {
|
||||
endSession("Вы вышли из сеанса");
|
||||
});
|
||||
|
||||
if (token) {
|
||||
|
||||
@@ -5,60 +5,101 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="theme-color" content="#101714">
|
||||
<title>Домовой - чат</title>
|
||||
<title>Домовой — твой помощник</title>
|
||||
<link rel="stylesheet" href="/assets/chat.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="chat-shell">
|
||||
<section id="login-view" class="login-panel">
|
||||
<div class="brand-mark" aria-hidden="true">Д</div>
|
||||
<h1>Домовой</h1>
|
||||
<form id="login-form" class="login-form">
|
||||
<label for="username">Логин</label>
|
||||
<input id="username" autocomplete="username" required autofocus>
|
||||
<label for="password">Пароль</label>
|
||||
<input id="password" type="password" autocomplete="current-password" required>
|
||||
<button type="submit">Войти</button>
|
||||
<p id="login-error" role="alert"></p>
|
||||
</form>
|
||||
<main class="assistant-shell">
|
||||
<section id="login-view" class="auth-view">
|
||||
<div class="auth-card" aria-live="polite">
|
||||
<div class="brand-block">
|
||||
<span class="brand-mark" aria-hidden="true">Д</span>
|
||||
<div>
|
||||
<p class="brand-kicker">личный диалог</p>
|
||||
<h1>Домовой</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p class="auth-copy">Войдите, чтобы открыть спокойный чат с вашим помощником.</p>
|
||||
<form id="login-form" class="login-form">
|
||||
<label for="username">Логин</label>
|
||||
<input id="username" autocomplete="username" required autofocus>
|
||||
<label for="password">Пароль</label>
|
||||
<div class="input-with-action">
|
||||
<input id="password" type="password" autocomplete="current-password" required>
|
||||
<button id="toggle-password" class="icon-button" type="button" aria-label="Показать пароль">показать</button>
|
||||
</div>
|
||||
<button id="login-button" type="submit">Открыть чат</button>
|
||||
<p id="login-error" role="alert"></p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="chat-view" class="chat-view" hidden>
|
||||
<header class="chat-header">
|
||||
<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="/" class="operator-link">Оператор</a>
|
||||
</nav>
|
||||
<span id="active-focus">фокус: не выбран</span>
|
||||
<span id="session-state">сессия активна</span>
|
||||
<button id="logout" type="button" aria-label="Выйти">↗</button>
|
||||
<header class="assistant-header">
|
||||
<div class="header-brand">
|
||||
<span class="brand-mark" aria-hidden="true">Д</span>
|
||||
<div>
|
||||
<p class="brand-kicker">твой спокойный ассистент</p>
|
||||
<h1>Домовой</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="session-state" aria-live="polite">
|
||||
<span id="connection-state" class="status-pill"></span>
|
||||
<button id="logout" class="ghost-button" type="button" aria-label="Выйти">Выйти</button>
|
||||
</div>
|
||||
</header>
|
||||
<section id="chat-panel" class="surface-panel active-panel">
|
||||
|
||||
<nav class="human-nav" aria-label="Разделы Домового" role="tablist">
|
||||
<button class="nav-tab active" data-tab="chat-panel" role="tab" aria-selected="true" aria-controls="chat-panel" id="tab-chat" type="button">Чат</button>
|
||||
<button class="nav-tab" data-tab="today-panel" role="tab" aria-selected="false" aria-controls="today-panel" id="tab-today" type="button">Сегодня</button>
|
||||
<button class="nav-tab" data-tab="history-panel" role="tab" aria-selected="false" aria-controls="history-panel" id="tab-history" type="button">История</button>
|
||||
</nav>
|
||||
|
||||
<section class="surface-panel active-panel" id="chat-panel" role="tabpanel" aria-labelledby="tab-chat">
|
||||
<div class="panel-toolbar">
|
||||
<div>
|
||||
<h2>Чат</h2>
|
||||
<p>Пишите обычные фразы. Я отвечу спокойно и по делу.</p>
|
||||
</div>
|
||||
<p id="active-focus" class="focus-chip" hidden></p>
|
||||
</div>
|
||||
<div id="messages" class="messages" aria-live="polite"></div>
|
||||
<form id="chat-form" class="composer">
|
||||
<textarea id="message" rows="3" maxlength="16384" placeholder="Напишите Домовому обычное сообщение..." required></textarea>
|
||||
<button id="send" type="submit">Отправить</button>
|
||||
<label class="visually-hidden" for="message">Ваше сообщение</label>
|
||||
<textarea id="message" rows="3" maxlength="16384" placeholder="Напишите, чем помочь" required></textarea>
|
||||
<div class="composer-actions">
|
||||
<button id="send" class="primary-button" type="submit">Отправить</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<section id="today-panel" class="surface-panel attention-surface" hidden>
|
||||
<div class="surface-heading">
|
||||
<p>Сегодня / внимание</p>
|
||||
<button id="refresh-today" type="button">Обновить</button>
|
||||
|
||||
<section id="today-panel" class="surface-panel attention-surface" hidden role="tabpanel" aria-labelledby="tab-today">
|
||||
<div class="panel-toolbar">
|
||||
<div>
|
||||
<h2>Сегодня</h2>
|
||||
<p id="today-subtitle">Что сейчас требует твоего внимания</p>
|
||||
</div>
|
||||
<button id="refresh-today" class="ghost-button" type="button">Обновить</button>
|
||||
</div>
|
||||
<div id="today-list" class="attention-list"></div>
|
||||
</section>
|
||||
<section id="history-panel" class="surface-panel history-surface" hidden>
|
||||
<div class="surface-heading">
|
||||
<p>История</p>
|
||||
<span>Последние сообщения текущего окна</span>
|
||||
|
||||
<section id="history-panel" class="surface-panel history-surface" hidden role="tabpanel" aria-labelledby="tab-history">
|
||||
<div class="panel-toolbar">
|
||||
<div>
|
||||
<h2>История</h2>
|
||||
<p>Сообщения текущего браузерного сеанса</p>
|
||||
</div>
|
||||
<button id="clear-history" class="ghost-button" type="button">Очистить</button>
|
||||
</div>
|
||||
<div id="history-list" class="history-list"></div>
|
||||
</section>
|
||||
</section>
|
||||
<p id="status-bar" class="status-bar" aria-live="polite"></p>
|
||||
</main>
|
||||
|
||||
<div id="composer-error" class="composer-error" role="status" aria-live="polite"></div>
|
||||
|
||||
<script src="/assets/chat.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"task_id": "DEV-000074",
|
||||
"task_file": "dev/tasks/DEV-000072-semantic-interpretation-deferral-boundary.json",
|
||||
"task_id": "DEV-000072",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"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,76 +0,0 @@
|
||||
# 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.
|
||||
|
||||
+1
-15
@@ -1,20 +1,6 @@
|
||||
# Assistant project status
|
||||
|
||||
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)`
|
||||
Audit date: 2026-08-17. Active implementation: DEV-000072 revision 1.
|
||||
|
||||
## DEV-000072 checkpoint
|
||||
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
# 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
|
||||
@@ -1,166 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
}
|
||||
]
|
||||
@@ -1,184 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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,10 +125,6 @@ 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, "расска", "раска", "объяс", "опиш", "покаж", "подскаж")) &&
|
||||
@@ -174,22 +170,6 @@ 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(".", " ", ",", " ", "!", " ", "?", " ", ":", " ", ";", " ", "(", " ", ")", " ", "\"", " ", "'", " ", "«", " ", "»", " ", "+", " + ", "-", " - ", "/", " / ")
|
||||
@@ -389,9 +369,6 @@ 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,25 +93,6 @@ 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,10 +30,6 @@ 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),
|
||||
@@ -42,7 +38,7 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
|
||||
SecondarySpeechAct: selectedCandidate.SecondarySpeechAct,
|
||||
ConfidenceMillis: selectedCandidate.ConfidenceMillis,
|
||||
Source: string(selectedCandidate.Source),
|
||||
AssertionVerification: assertionVerification,
|
||||
AssertionVerification: string(epistemics.VerificationUnverified),
|
||||
RequestedOutcome: selectedCandidate.RequestedOutcome,
|
||||
Clarification: selectedCandidate.Clarification,
|
||||
NeedsCognition: selectedCandidate.NeedsCognition,
|
||||
|
||||
@@ -66,16 +66,6 @@ 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