chati-dev 4.5.14 → 4.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/framework/config.yaml +3 -3
- package/framework/context/protocols.md +1 -1
- package/framework/context/root.md +1 -1
- package/framework/i18n/en.yaml +4 -0
- package/framework/i18n/es.yaml +4 -0
- package/framework/i18n/fr.yaml +4 -0
- package/framework/i18n/pt.yaml +4 -0
- package/framework/manifest.json +20 -20
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +29 -2
- package/framework/orchestrator/chati.md +15 -1
- package/framework/schemas/session.schema.json +21 -1
- package/package.json +1 -1
- package/src/installer/core.js +9 -3
- package/src/installer/templates.js +31 -8
- package/src/intelligence/timeline.js +3 -1
- package/src/orchestrator/cli.js +101 -7
- package/src/orchestrator/doctor.js +74 -4
- package/src/orchestrator/session-manager.js +84 -1
- package/src/terminal/prompt-builder.js +1 -0
- package/src/terminal/run-agent.js +60 -2
- package/src/utils/schema-validator.js +5 -0
- package/src/wizard/feedback.js +11 -0
- package/src/wizard/i18n.js +4 -0
- package/src/wizard/index.js +17 -7
package/framework/config.yaml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# chati.dev Configuration
|
|
2
|
-
version: "4.5.
|
|
2
|
+
version: "4.5.16"
|
|
3
3
|
installed_at: "2026-02-07T10:00:00Z"
|
|
4
|
-
updated_at: "2026-08-
|
|
5
|
-
installer_version: "4.5.
|
|
4
|
+
updated_at: "2026-08-28T00:00:00Z"
|
|
5
|
+
installer_version: "4.5.16"
|
|
6
6
|
project_type: greenfield
|
|
7
7
|
language: en
|
|
8
8
|
ides: []
|
|
@@ -21,7 +21,7 @@ Agent retries on failure. Investigate root cause before escalating.
|
|
|
21
21
|
## 6. Language Protocol
|
|
22
22
|
- **Interaction**: User's preferred language
|
|
23
23
|
- **Artifacts**: Always English
|
|
24
|
-
- **
|
|
24
|
+
- **Every visible interaction**: Use the language selected during installation, starting with the first response. This includes prefaces, progress updates, questions, errors, pauses, and resumptions.
|
|
25
25
|
|
|
26
26
|
## 7. Deviation Protocol
|
|
27
27
|
- Deviations handled by orchestrator (not separate agent)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Chati.dev System Context
|
|
2
2
|
|
|
3
3
|
## Framework
|
|
4
|
-
- **Version**: 4.5.
|
|
4
|
+
- **Version**: 4.5.16
|
|
5
5
|
- **Agents**: Specialized discovery and planning agents feeding provider-neutral RAIL execution and a sealed release lane
|
|
6
6
|
- **Constitution**: 25 Articles + Preamble
|
|
7
7
|
- **Quality**: 5 pipeline gates + 3-tier verdicts + Fault Vector Protocol (Article XXII)
|
package/framework/i18n/en.yaml
CHANGED
|
@@ -100,6 +100,9 @@ installer:
|
|
|
100
100
|
system_prerequisites_failed: "System prerequisites not met:"
|
|
101
101
|
fix_prerequisites: "Fix the issues above and try again."
|
|
102
102
|
system_check: "System check:"
|
|
103
|
+
system_checking: "Checking system requirements..."
|
|
104
|
+
catalog_resolving: "Loading the signed model catalog..."
|
|
105
|
+
internal_authorizing: "Verifying access to Internal mode..."
|
|
103
106
|
created_provider_claude: "Created .claude/commands/ (thin router)"
|
|
104
107
|
created_provider_gemini: "Created .gemini/commands/ (TOML command)"
|
|
105
108
|
created_provider_codex: "Created .agents/skills/chati/ (Codex skill)"
|
|
@@ -114,6 +117,7 @@ installer:
|
|
|
114
117
|
license_placeholder: "CHATI-XXXX-XXXX-XXXX"
|
|
115
118
|
license_skipped: "Skipped. Run: npx chati-dev activate --key=YOUR-KEY when ready."
|
|
116
119
|
license_activating: "Activating license..."
|
|
120
|
+
license_verifying: "Verifying saved license..."
|
|
117
121
|
license_activated: "License activated! Plan: {plan}, {days} day(s) remaining."
|
|
118
122
|
license_activation_failed: "Activation failed: {error}"
|
|
119
123
|
license_retry: "Run: npx chati-dev activate --key=YOUR-KEY to try again."
|
package/framework/i18n/es.yaml
CHANGED
|
@@ -100,6 +100,9 @@ installer:
|
|
|
100
100
|
system_prerequisites_failed: "No se cumplen los requisitos previos del sistema:"
|
|
101
101
|
fix_prerequisites: "Corrija los problemas anteriores e intentelo de nuevo."
|
|
102
102
|
system_check: "Verificacion del sistema:"
|
|
103
|
+
system_checking: "Verificando los requisitos del sistema..."
|
|
104
|
+
catalog_resolving: "Cargando el catalogo firmado de modelos..."
|
|
105
|
+
internal_authorizing: "Verificando el acceso al modo Internal..."
|
|
103
106
|
created_provider_claude: "Creado .claude/commands/ (enrutador ligero)"
|
|
104
107
|
created_provider_gemini: "Creado .gemini/commands/ (comando TOML)"
|
|
105
108
|
created_provider_codex: "Creado .agents/skills/chati/ (skill de Codex)"
|
|
@@ -114,6 +117,7 @@ installer:
|
|
|
114
117
|
license_placeholder: "CHATI-XXXX-XXXX-XXXX"
|
|
115
118
|
license_skipped: "Paso omitido. Cuando quiera, ejecute: npx chati-dev activate --key=SU-CLAVE."
|
|
116
119
|
license_activating: "Activando la licencia..."
|
|
120
|
+
license_verifying: "Verificando la licencia guardada..."
|
|
117
121
|
license_activated: "Licencia activada! Plan: {plan}, {days} dia(s) restante(s)."
|
|
118
122
|
license_activation_failed: "Fallo de activacion: {error}"
|
|
119
123
|
license_retry: "Ejecute: npx chati-dev activate --key=SU-CLAVE para intentarlo de nuevo."
|
package/framework/i18n/fr.yaml
CHANGED
|
@@ -100,6 +100,9 @@ installer:
|
|
|
100
100
|
system_prerequisites_failed: "Les prerequis systeme ne sont pas satisfaits:"
|
|
101
101
|
fix_prerequisites: "Corrigez les problemes ci-dessus et reessayez."
|
|
102
102
|
system_check: "Verification du systeme:"
|
|
103
|
+
system_checking: "Verification des prerequis du systeme..."
|
|
104
|
+
catalog_resolving: "Chargement du catalogue de modeles signe..."
|
|
105
|
+
internal_authorizing: "Verification de l'acces au mode Internal..."
|
|
103
106
|
created_provider_claude: "Cree .claude/commands/ (routeur leger)"
|
|
104
107
|
created_provider_gemini: "Cree .gemini/commands/ (commande TOML)"
|
|
105
108
|
created_provider_codex: "Cree .agents/skills/chati/ (skill Codex)"
|
|
@@ -114,6 +117,7 @@ installer:
|
|
|
114
117
|
license_placeholder: "CHATI-XXXX-XXXX-XXXX"
|
|
115
118
|
license_skipped: "Etape ignoree. Lorsque vous le souhaitez, executez: npx chati-dev activate --key=VOTRE-CLE."
|
|
116
119
|
license_activating: "Activation de la licence..."
|
|
120
|
+
license_verifying: "Verification de la licence enregistree..."
|
|
117
121
|
license_activated: "Licence activee! Offre: {plan}, {days} jour(s) restant(s)."
|
|
118
122
|
license_activation_failed: "Echec de l'activation: {error}"
|
|
119
123
|
license_retry: "Executez: npx chati-dev activate --key=VOTRE-CLE pour reessayer."
|
package/framework/i18n/pt.yaml
CHANGED
|
@@ -100,6 +100,9 @@ installer:
|
|
|
100
100
|
system_prerequisites_failed: "Os pre-requisitos do sistema nao foram atendidos:"
|
|
101
101
|
fix_prerequisites: "Corrija os problemas acima e tente novamente."
|
|
102
102
|
system_check: "Verificacao do sistema:"
|
|
103
|
+
system_checking: "Verificando os requisitos do sistema..."
|
|
104
|
+
catalog_resolving: "Carregando o catalogo assinado de modelos..."
|
|
105
|
+
internal_authorizing: "Validando o acesso ao modo Internal..."
|
|
103
106
|
created_provider_claude: "Criado .claude/commands/ (roteador leve)"
|
|
104
107
|
created_provider_gemini: "Criado .gemini/commands/ (comando TOML)"
|
|
105
108
|
created_provider_codex: "Criado .agents/skills/chati/ (skill do Codex)"
|
|
@@ -114,6 +117,7 @@ installer:
|
|
|
114
117
|
license_placeholder: "CHATI-XXXX-XXXX-XXXX"
|
|
115
118
|
license_skipped: "Etapa ignorada. Quando quiser, execute: npx chati-dev activate --key=SUA-CHAVE."
|
|
116
119
|
license_activating: "Ativando a licenca..."
|
|
120
|
+
license_verifying: "Validando a licenca salva..."
|
|
117
121
|
license_activated: "Licenca ativada! Plano: {plan}, {days} dia(s) restante(s)."
|
|
118
122
|
license_activation_failed: "Falha na ativacao: {error}"
|
|
119
123
|
license_retry: "Execute: npx chati-dev activate --key=SUA-CHAVE para tentar novamente."
|
package/framework/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"createdAt": "2026-08-
|
|
2
|
+
"createdAt": "2026-08-28T12:19:47.274Z",
|
|
3
3
|
"files": {
|
|
4
4
|
"agents/build/dev.md": {
|
|
5
5
|
"hash": "f0dd9bcdf44c57fdf64beca65d3617f0f24467b2f6edc9c0577d721b4922977a",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"type": "md"
|
|
98
98
|
},
|
|
99
99
|
"config.yaml": {
|
|
100
|
-
"hash": "
|
|
100
|
+
"hash": "66f9a59f21944288544a7aeb5bb0316c3c65be1162faa0db896f5346d8f89877",
|
|
101
101
|
"size": 2638,
|
|
102
102
|
"type": "yaml"
|
|
103
103
|
},
|
|
@@ -112,8 +112,8 @@
|
|
|
112
112
|
"type": "md"
|
|
113
113
|
},
|
|
114
114
|
"context/protocols.md": {
|
|
115
|
-
"hash": "
|
|
116
|
-
"size":
|
|
115
|
+
"hash": "a80110e083cd4e301173353fa2748e096bdb7a09ffad9e6302ba5075fbf11cee",
|
|
116
|
+
"size": 1410,
|
|
117
117
|
"type": "md"
|
|
118
118
|
},
|
|
119
119
|
"context/quality.md": {
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"type": "md"
|
|
123
123
|
},
|
|
124
124
|
"context/root.md": {
|
|
125
|
-
"hash": "
|
|
125
|
+
"hash": "a226a365f64a28bf952c399dcc97962202bd195fa2a75fd4b894293a1503569d",
|
|
126
126
|
"size": 1333,
|
|
127
127
|
"type": "md"
|
|
128
128
|
},
|
|
@@ -377,23 +377,23 @@
|
|
|
377
377
|
"type": "js"
|
|
378
378
|
},
|
|
379
379
|
"i18n/en.yaml": {
|
|
380
|
-
"hash": "
|
|
381
|
-
"size":
|
|
380
|
+
"hash": "dbaf6e3da9820a933bd6b358e5dcce49057c7b34c7855d6f7f443833202f347a",
|
|
381
|
+
"size": 8307,
|
|
382
382
|
"type": "yaml"
|
|
383
383
|
},
|
|
384
384
|
"i18n/es.yaml": {
|
|
385
|
-
"hash": "
|
|
386
|
-
"size":
|
|
385
|
+
"hash": "047a14e7fa763179002c265f2df3bf0e9a407d5fc8b8fac0ff05ae92562f11eb",
|
|
386
|
+
"size": 8941,
|
|
387
387
|
"type": "yaml"
|
|
388
388
|
},
|
|
389
389
|
"i18n/fr.yaml": {
|
|
390
|
-
"hash": "
|
|
391
|
-
"size":
|
|
390
|
+
"hash": "8353e42d8124546109833a123143e905dbb976abe658259d3782aa5d1190d0b4",
|
|
391
|
+
"size": 9090,
|
|
392
392
|
"type": "yaml"
|
|
393
393
|
},
|
|
394
394
|
"i18n/pt.yaml": {
|
|
395
|
-
"hash": "
|
|
396
|
-
"size":
|
|
395
|
+
"hash": "9286764e634c88b1cf8f49f9239d48ff5a2bbef7fe207d61d69d70c501bdca8b",
|
|
396
|
+
"size": 8812,
|
|
397
397
|
"type": "yaml"
|
|
398
398
|
},
|
|
399
399
|
"intelligence/confidence.yaml": {
|
|
@@ -462,8 +462,8 @@
|
|
|
462
462
|
"type": "yaml"
|
|
463
463
|
},
|
|
464
464
|
"orchestrator/chati-router.js": {
|
|
465
|
-
"hash": "
|
|
466
|
-
"size":
|
|
465
|
+
"hash": "402033380d5becfb73d77c4c617740e7f32b2abf1c42bf6b9e751b97bd32c876",
|
|
466
|
+
"size": 23194,
|
|
467
467
|
"type": "js"
|
|
468
468
|
},
|
|
469
469
|
"orchestrator/chati-update.md": {
|
|
@@ -472,8 +472,8 @@
|
|
|
472
472
|
"type": "md"
|
|
473
473
|
},
|
|
474
474
|
"orchestrator/chati.md": {
|
|
475
|
-
"hash": "
|
|
476
|
-
"size":
|
|
475
|
+
"hash": "b0e7395fa4ae0119e8df721e0f97c04d740c119be5f260673aea14b5672e96ff",
|
|
476
|
+
"size": 46948,
|
|
477
477
|
"type": "md"
|
|
478
478
|
},
|
|
479
479
|
"package.json": {
|
|
@@ -857,8 +857,8 @@
|
|
|
857
857
|
"type": "json"
|
|
858
858
|
},
|
|
859
859
|
"schemas/session.schema.json": {
|
|
860
|
-
"hash": "
|
|
861
|
-
"size":
|
|
860
|
+
"hash": "553b5bf78761f8dd50f98b1e638d42c1bde473d1c0e562b087b3f26cc8c3585b",
|
|
861
|
+
"size": 19979,
|
|
862
862
|
"type": "json"
|
|
863
863
|
},
|
|
864
864
|
"schemas/task.schema.json": {
|
|
@@ -1352,5 +1352,5 @@
|
|
|
1352
1352
|
"type": "yaml"
|
|
1353
1353
|
}
|
|
1354
1354
|
},
|
|
1355
|
-
"version": "4.5.
|
|
1355
|
+
"version": "4.5.16"
|
|
1356
1356
|
}
|
package/framework/manifest.sig
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
pn6mHfeHH1rfvjDzyoAh8karYWO61dtxogu3tZgcmrb8Q07rgK3jUK0AS4ThiCqIVDiyGOpp0df6GjiZ1meGAg==
|
|
@@ -19,7 +19,12 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
19
19
|
import { join } from 'path';
|
|
20
20
|
|
|
21
21
|
const PROJECT_DIR = process.cwd();
|
|
22
|
-
const
|
|
22
|
+
const RAW_ROUTER_ARGS = process.argv.slice(2);
|
|
23
|
+
const harnessFlagIndex = RAW_ROUTER_ARGS.indexOf('--harness');
|
|
24
|
+
const ACTIVE_HARNESS = harnessFlagIndex >= 0 ? RAW_ROUTER_ARGS[harnessFlagIndex + 1] : null;
|
|
25
|
+
const ROUTER_ARGS = harnessFlagIndex >= 0
|
|
26
|
+
? RAW_ROUTER_ARGS.filter((_, index) => index !== harnessFlagIndex && index !== harnessFlagIndex + 1)
|
|
27
|
+
: RAW_ROUTER_ARGS;
|
|
23
28
|
|
|
24
29
|
async function readEnabledHarnesses(projectDir) {
|
|
25
30
|
const installationPath = join(projectDir, '.chati', 'v2', 'installation.json');
|
|
@@ -312,6 +317,7 @@ const VALID_ORCHESTRATE_SUBS = new Set([
|
|
|
312
317
|
'spawn-team', 'team-status', 'team-dissolve',
|
|
313
318
|
'wait-for-capture', 'wave-status', 'assess-coverage',
|
|
314
319
|
'wait-for-license', 'doctor', 'run-script-task',
|
|
320
|
+
'activate-harness',
|
|
315
321
|
]);
|
|
316
322
|
|
|
317
323
|
const VALID_RAIL_SUBS = new Set(['rail-next']);
|
|
@@ -486,13 +492,34 @@ async function main() {
|
|
|
486
492
|
pipeline: null,
|
|
487
493
|
team: null,
|
|
488
494
|
error: null,
|
|
495
|
+
active_harness: ACTIVE_HARNESS,
|
|
489
496
|
};
|
|
490
497
|
|
|
498
|
+
const canonicalSessionPath = join(PROJECT_DIR, '.chati', 'session.yaml');
|
|
499
|
+
if (ACTIVE_HARNESS && existsSync(canonicalSessionPath)) {
|
|
500
|
+
const cliMod = await findCliModule(PROJECT_DIR);
|
|
501
|
+
if (!cliMod) {
|
|
502
|
+
result.ok = false;
|
|
503
|
+
result.action = 'error';
|
|
504
|
+
result.error = 'cli_module_not_found';
|
|
505
|
+
console.log(JSON.stringify(result));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const activation = await captureOrchestrate(cliMod, 'activate-harness', ['--harness', ACTIVE_HARNESS], PROJECT_DIR);
|
|
509
|
+
if (!activation.activated) {
|
|
510
|
+
result.ok = false;
|
|
511
|
+
result.action = 'error';
|
|
512
|
+
result.error = activation.error || 'harness_activation_failed';
|
|
513
|
+
console.log(JSON.stringify(result));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
491
518
|
// -----------------------------------------------------------------------
|
|
492
519
|
// 1. Session state
|
|
493
520
|
// -----------------------------------------------------------------------
|
|
494
521
|
try {
|
|
495
|
-
const sessionPath =
|
|
522
|
+
const sessionPath = canonicalSessionPath;
|
|
496
523
|
if (existsSync(sessionPath)) {
|
|
497
524
|
const raw = readFileSync(sessionPath, 'utf-8');
|
|
498
525
|
const extract = (key) => {
|
|
@@ -103,6 +103,7 @@ Parse the JSON output. The `action` field tells you what to do:
|
|
|
103
103
|
| `setup` | Action: Setup |
|
|
104
104
|
| `activate_interactive` | Action: Interactive Agent |
|
|
105
105
|
| `spawn_routed_interactive` | Action: Routed Interactive Agent |
|
|
106
|
+
| `resume_pending_interaction` | Action: Resume Pending Interaction |
|
|
106
107
|
| `spawn_autonomous` | Action: Autonomous Agent |
|
|
107
108
|
| `spawn_parallel` | Action: Parallel Spawn |
|
|
108
109
|
| `spawn_team` | Action: Spawn Team (Article XXI) |
|
|
@@ -204,6 +205,19 @@ internal router.
|
|
|
204
205
|
4. The opened CLI remains the conversation orchestrator. The routed process
|
|
205
206
|
does not become a framework-wide primary CLI.
|
|
206
207
|
|
|
208
|
+
## Action: Resume Pending Interaction
|
|
209
|
+
|
|
210
|
+
The routed process already asked a question and the canonical session stored
|
|
211
|
+
it. Do not execute `spawn_command` yet and do not dispatch the agent again.
|
|
212
|
+
|
|
213
|
+
1. Present only the exact `question` returned by the router, using
|
|
214
|
+
`session.language`.
|
|
215
|
+
2. When the user answers, execute the returned `spawn_command` once with
|
|
216
|
+
`--additional-context "{user_response}"` appended.
|
|
217
|
+
3. Parse the new result using Action: Routed Interactive Agent.
|
|
218
|
+
4. Read `relay_cycles` from the router response. After three unresolved relay cycles, use the configured reasoning
|
|
219
|
+
escalation. Do not invent an answer or silently switch providers.
|
|
220
|
+
|
|
207
221
|
---
|
|
208
222
|
|
|
209
223
|
## Completion Options (Protocol 3 - Guided Options)
|
|
@@ -977,7 +991,7 @@ When the independent RAIL reviewer classifies a fault:
|
|
|
977
991
|
- Read language from JSON `session.language` field
|
|
978
992
|
- ALL interactions, guidance, questions, options in that language
|
|
979
993
|
- ALL artifacts, handoffs, templates in English (enforced by Constitution Article VII)
|
|
980
|
-
-
|
|
994
|
+
- Every visible interaction from the first response onward follows `session.language`, including prefaces, progress messages, questions, errors, pauses, and resumptions.
|
|
981
995
|
|
|
982
996
|
---
|
|
983
997
|
|
|
@@ -85,6 +85,26 @@
|
|
|
85
85
|
"type": "string",
|
|
86
86
|
"description": "Currently active agent name"
|
|
87
87
|
},
|
|
88
|
+
"active_harness": {
|
|
89
|
+
"type": ["string", "null"],
|
|
90
|
+
"enum": ["claude", "codex", "grok", null],
|
|
91
|
+
"default": null,
|
|
92
|
+
"description": "CLI or UI currently orchestrating the conversation. Independent from task provider routing."
|
|
93
|
+
},
|
|
94
|
+
"session_status": {
|
|
95
|
+
"enum": ["inactive", "active", "paused", "completed"],
|
|
96
|
+
"default": "inactive"
|
|
97
|
+
},
|
|
98
|
+
"current_execution_binding": {
|
|
99
|
+
"type": ["object", "null"],
|
|
100
|
+
"default": null,
|
|
101
|
+
"description": "Exact provider, model and reasoning binding for the active routed task."
|
|
102
|
+
},
|
|
103
|
+
"pending_interaction": {
|
|
104
|
+
"type": ["object", "null"],
|
|
105
|
+
"default": null,
|
|
106
|
+
"description": "Durable interactive relay, including the exact unanswered question and its execution binding."
|
|
107
|
+
},
|
|
88
108
|
"language": {
|
|
89
109
|
"enum": ["en", "pt", "es", "fr"],
|
|
90
110
|
"default": "en",
|
|
@@ -122,7 +142,7 @@
|
|
|
122
142
|
"type": "object",
|
|
123
143
|
"required": ["status", "score", "criteria_count"],
|
|
124
144
|
"properties": {
|
|
125
|
-
"status": { "enum": ["pending", "in_progress", "completed", "skipped", "needs_revalidation"] },
|
|
145
|
+
"status": { "enum": ["pending", "in_progress", "waiting_for_user", "completed", "skipped", "needs_revalidation"] },
|
|
126
146
|
"score": { "type": "number", "minimum": 0, "maximum": 100 },
|
|
127
147
|
"criteria_count": { "type": "integer", "minimum": 0 },
|
|
128
148
|
"completed_at": { "type": ["string", "null"], "format": "date-time" }
|
package/package.json
CHANGED
package/src/installer/core.js
CHANGED
|
@@ -182,7 +182,13 @@ export async function installFramework(config) {
|
|
|
182
182
|
// Write config.yaml
|
|
183
183
|
writeFileSync(
|
|
184
184
|
join(frameworkDir, 'config.yaml'),
|
|
185
|
-
generateConfigYaml({
|
|
185
|
+
generateConfigYaml({
|
|
186
|
+
version, projectType, language, selectedIDEs,
|
|
187
|
+
allProviders: enabledProviders,
|
|
188
|
+
runtimeVersion: config.runtimeVersion,
|
|
189
|
+
installationMode: config.installationMode,
|
|
190
|
+
clickupPreflight: config.clickupPreflight,
|
|
191
|
+
}),
|
|
186
192
|
'utf-8'
|
|
187
193
|
);
|
|
188
194
|
|
|
@@ -481,7 +487,7 @@ async function configureIDE(targetDir, ideKey, selectedMCPs, options = {}) {
|
|
|
481
487
|
|
|
482
488
|
Run via Bash:
|
|
483
489
|
\`\`\`
|
|
484
|
-
node .chati.dev/orchestrator/chati-router.js
|
|
490
|
+
node .chati.dev/orchestrator/chati-router.js --harness claude
|
|
485
491
|
\`\`\`
|
|
486
492
|
|
|
487
493
|
Parse the JSON output. This single command already handled: license validation, integrity check, session state, pipeline routing, and team registration (if needed). You do NOT need to run any of these separately.
|
|
@@ -497,7 +503,7 @@ Parse the JSON output. This single command already handled: license validation,
|
|
|
497
503
|
|
|
498
504
|
### If \`ok\` is true:
|
|
499
505
|
|
|
500
|
-
1. Use \`session.language\` for
|
|
506
|
+
1. Use \`session.language\` for every visible response, preface, progress message, question and error, starting with the first response (pt = Português, en = English, es = Español, fr = Français).
|
|
501
507
|
2. Read the full orchestrator at \`${orchestratorPath || '.chati.dev/orchestrator/chati.md'}\`.
|
|
502
508
|
3. The \`action\` field tells you what to do. Go to the matching "Action:" section in the orchestrator.
|
|
503
509
|
4. **If \`action\` is \`spawn_team\`**: the team is ALREADY registered (Step 1 already ran as code). The \`team\` object contains \`team_id\`, \`task_list_path\`, \`mailbox_path\`, \`members\`. Go directly to "Action: Spawn Team" Step 2 and use these values in the agent prompts. DO NOT run \`orchestrate spawn-team\` again — it already ran.
|
|
@@ -55,6 +55,10 @@ export function generateSessionYaml(config) {
|
|
|
55
55
|
scaffold_applied: [],
|
|
56
56
|
active_model: null,
|
|
57
57
|
active_provider: null,
|
|
58
|
+
active_harness: null,
|
|
59
|
+
session_status: 'inactive',
|
|
60
|
+
current_execution_binding: null,
|
|
61
|
+
pending_interaction: null,
|
|
58
62
|
context_tokens_used: 0,
|
|
59
63
|
context_window_tokens: null,
|
|
60
64
|
context_last_bracket: null,
|
|
@@ -119,6 +123,13 @@ export function generateConfigYaml(config) {
|
|
|
119
123
|
codex: { enabled: providers.includes('codex') || selectedIDEs.includes('codex-cli') },
|
|
120
124
|
grok: { enabled: providers.includes('grok') || selectedIDEs.includes('grok-cli') },
|
|
121
125
|
},
|
|
126
|
+
integrations: {
|
|
127
|
+
clickup: {
|
|
128
|
+
required: config.installationMode === 'internal',
|
|
129
|
+
connected_harnesses: config.clickupPreflight?.connected_providers || [],
|
|
130
|
+
durable_projection_queue: '.chati/v2/tracking/pending-projections',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
122
133
|
};
|
|
123
134
|
|
|
124
135
|
// Feature flags (Intelligence Upgrade + Article XXI Agent Teams)
|
|
@@ -228,7 +239,7 @@ description: >
|
|
|
228
239
|
Use the shell in the current project directory and run:
|
|
229
240
|
|
|
230
241
|
\`\`\`bash
|
|
231
|
-
node .chati.dev/orchestrator/chati-router.js
|
|
242
|
+
node .chati.dev/orchestrator/chati-router.js --harness codex
|
|
232
243
|
\`\`\`
|
|
233
244
|
|
|
234
245
|
Do not infer provider authentication, session state, model availability, or the
|
|
@@ -239,7 +250,8 @@ routed spawn command returns a provider authentication error.
|
|
|
239
250
|
Parse the JSON output before responding:
|
|
240
251
|
|
|
241
252
|
- If \`ok\` is \`false\`, report the exact \`error\` and relevant detail, then stop.
|
|
242
|
-
- If \`ok\` is \`true\`, use \`session.language\` for every response
|
|
253
|
+
- If \`ok\` is \`true\`, use \`session.language\` for every visible response,
|
|
254
|
+
preface, progress message, question and error, starting with the first response.
|
|
243
255
|
- Follow the returned \`action\` in the adapted orchestrator.
|
|
244
256
|
- When the action includes \`pipeline.spawn_command\`, execute that exact command.
|
|
245
257
|
Never replace it with an inferred provider check or a manually constructed command.
|
|
@@ -256,8 +268,9 @@ Parse the JSON output before responding:
|
|
|
256
268
|
|
|
257
269
|
## CRITICAL: Language Override
|
|
258
270
|
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
Use the canonical \`session.language\` returned by the router. ALL visible
|
|
272
|
+
responses MUST use this language from the first response onward. This
|
|
273
|
+
overrides any global setting.
|
|
261
274
|
|
|
262
275
|
| Value | Language |
|
|
263
276
|
|-------|----------|
|
|
@@ -296,7 +309,7 @@ export function generateGeminiRouter(options = {}) {
|
|
|
296
309
|
prompt = """
|
|
297
310
|
CRITICAL — Language Override:
|
|
298
311
|
Read .chati/session.yaml field "language" BEFORE anything else.
|
|
299
|
-
|
|
312
|
+
Every visible response, including the first response, MUST be in this language (en, pt, es, fr).
|
|
300
313
|
If session.yaml does not exist or has no language field, default to English.
|
|
301
314
|
|
|
302
315
|
The orchestrator and all agent files are pre-configured for Gemini CLI.
|
|
@@ -323,13 +336,23 @@ export function generateGrokRouter(options = {}) {
|
|
|
323
336
|
const orchPath = options.orchestratorPath || '.chati.dev/orchestrator/chati.md';
|
|
324
337
|
return `# Chati.dev Orchestrator
|
|
325
338
|
|
|
326
|
-
|
|
327
|
-
|
|
339
|
+
## STEP 0: Run the deterministic router first
|
|
340
|
+
|
|
341
|
+
Before reading mutable session files or responding, run:
|
|
342
|
+
|
|
343
|
+
\`\`\`bash
|
|
344
|
+
node .chati.dev/orchestrator/chati-router.js --harness grok
|
|
345
|
+
\`\`\`
|
|
346
|
+
|
|
347
|
+
Parse the JSON output. It is the source of truth for session state, task
|
|
348
|
+
routing and provider authentication. Use the returned \`session.language\` for
|
|
349
|
+
every visible response, preface, progress message, question and error,
|
|
350
|
+
starting with the first response. If absent, default to English.
|
|
328
351
|
|
|
329
352
|
The orchestrator and agent files are pre-configured for Grok CLI. Read and
|
|
330
353
|
execute the full orchestrator at \`${orchPath}\`.
|
|
331
354
|
|
|
332
|
-
|
|
355
|
+
Only after the router succeeds, load this context:
|
|
333
356
|
- \`.chati/session.yaml\`
|
|
334
357
|
- \`AGENTS.md\`
|
|
335
358
|
- \`.grok/session-lock.md\`
|
|
@@ -33,6 +33,8 @@ export const EventType = {
|
|
|
33
33
|
PROVIDER_SELECTED: 'provider_selected',
|
|
34
34
|
SESSION_STARTED: 'session_started',
|
|
35
35
|
SESSION_ENDED: 'session_ended',
|
|
36
|
+
HARNESS_ACTIVATED: 'harness_activated',
|
|
37
|
+
AGENT_WAITING_FOR_INPUT: 'agent_waiting_for_input',
|
|
36
38
|
CORRECTION_TRIGGERED: 'correction_triggered',
|
|
37
39
|
CORRECTION_ESCALATED: 'correction_escalated',
|
|
38
40
|
};
|
|
@@ -84,7 +86,7 @@ export function recordEvent(projectDir, type, agent, data = {}) {
|
|
|
84
86
|
// Prevents duplicate AGENT_ACTIVATED entries when handleAdvance chains into
|
|
85
87
|
// handleNext while the caller also invokes next separately.
|
|
86
88
|
const last = timeline[timeline.length - 1];
|
|
87
|
-
if (last && last.type === type && last.agent === agent) {
|
|
89
|
+
if (type === EventType.AGENT_ACTIVATED && last && last.type === type && last.agent === agent) {
|
|
88
90
|
return last;
|
|
89
91
|
}
|
|
90
92
|
|
package/src/orchestrator/cli.js
CHANGED
|
@@ -799,6 +799,35 @@ function removeSessionLock(projectDir, resumeMsg) {
|
|
|
799
799
|
async function handleNext(projectDir) {
|
|
800
800
|
const result = await _handleNextInner(projectDir);
|
|
801
801
|
|
|
802
|
+
// The canonical session owns the active task. Native harness lock files are
|
|
803
|
+
// projections only, so a CLI switch cannot create a second execution.
|
|
804
|
+
// Persist only actions that actually dispatch an agent. Surface holds and
|
|
805
|
+
// RAIL handoffs are control-plane responses, not running pipeline agents.
|
|
806
|
+
const dispatchActions = new Set([
|
|
807
|
+
'spawn_routed_interactive',
|
|
808
|
+
'spawn_autonomous',
|
|
809
|
+
'spawn_parallel',
|
|
810
|
+
'spawn_team',
|
|
811
|
+
]);
|
|
812
|
+
if (result?.agent && dispatchActions.has(result.action)) {
|
|
813
|
+
const binding = result.model_info ? {
|
|
814
|
+
provider: result.model_info.provider || null,
|
|
815
|
+
provider_id: result.model_info.provider_id || null,
|
|
816
|
+
model: result.model_info.model || null,
|
|
817
|
+
reasoning_configuration: result.model_info.reasoning_configuration || null,
|
|
818
|
+
catalog_snapshot_ref: result.model_info.catalog_snapshot_ref || null,
|
|
819
|
+
} : null;
|
|
820
|
+
const persisted = updateSession(projectDir, {
|
|
821
|
+
current_agent: result.agent,
|
|
822
|
+
...(binding ? { current_execution_binding: binding } : {}),
|
|
823
|
+
agents: { [result.agent]: { status: 'in_progress' } },
|
|
824
|
+
});
|
|
825
|
+
if (!persisted.saved) {
|
|
826
|
+
return errorResult(`Could not persist canonical agent state: ${persisted.error}`, 'SESSION_WRITE_FAILED');
|
|
827
|
+
}
|
|
828
|
+
recordEvent(projectDir, EventType.AGENT_ACTIVATED, result.agent, { action: result.action, phase: result.phase });
|
|
829
|
+
}
|
|
830
|
+
|
|
802
831
|
// Fase 8: attach state-at-a-glance card to every non-error response.
|
|
803
832
|
// The card renders as a JSON string field; the terminal / orchestrator
|
|
804
833
|
// prompt can console.log it verbatim. Silent on setup/error responses
|
|
@@ -815,7 +844,7 @@ async function handleNext(projectDir) {
|
|
|
815
844
|
// Centralized session lock update: applies to ALL return paths.
|
|
816
845
|
// Activates lock when an agent is returned, deactivates on completion.
|
|
817
846
|
if (result && result.agent && result.action !== 'error' && result.action !== 'setup') {
|
|
818
|
-
const activating = ['activate_interactive', 'spawn_routed_interactive', 'spawn_autonomous', 'spawn_team', 'spawn_parallel'];
|
|
847
|
+
const activating = ['activate_interactive', 'spawn_routed_interactive', 'spawn_autonomous', 'spawn_team', 'spawn_parallel', 'resume_pending_interaction'];
|
|
819
848
|
if (activating.includes(result.action)) {
|
|
820
849
|
try {
|
|
821
850
|
const runtimePipeline = getRuntimeAgentPipeline(projectDir, result.session);
|
|
@@ -852,6 +881,40 @@ async function _handleNextInner(projectDir) {
|
|
|
852
881
|
|
|
853
882
|
const installationV2 = loadRuntimeInstallationV2(projectDir);
|
|
854
883
|
const runtimePipeline = getRuntimeAgentPipeline(projectDir, session);
|
|
884
|
+
|
|
885
|
+
const pending = session.pending_interaction;
|
|
886
|
+
if (pending?.status === 'waiting_for_user' && pending.agent && pending.question) {
|
|
887
|
+
const binding = pending.execution_binding || session.current_execution_binding || resolveAgentModel(pending.agent, projectDir);
|
|
888
|
+
const agentDef = getAgentDefinition(pending.agent);
|
|
889
|
+
return {
|
|
890
|
+
action: 'resume_pending_interaction',
|
|
891
|
+
agent: pending.agent,
|
|
892
|
+
agent_file: getAgentFile(pending.agent, projectDir) || null,
|
|
893
|
+
phase: agentDef?.phase || session.mode,
|
|
894
|
+
question: pending.question,
|
|
895
|
+
relay_cycles: pending.relay_cycles || 1,
|
|
896
|
+
spawn_command: buildSpawnCommand(
|
|
897
|
+
pending.agent,
|
|
898
|
+
projectDir,
|
|
899
|
+
session.last_handoff || 'none',
|
|
900
|
+
binding.provider,
|
|
901
|
+
600000,
|
|
902
|
+
binding.model,
|
|
903
|
+
Boolean(installationV2),
|
|
904
|
+
binding,
|
|
905
|
+
),
|
|
906
|
+
model_info: binding,
|
|
907
|
+
redispatched: false,
|
|
908
|
+
pipeline_progress: getPipelineProgress(sessionToPipelineState(session, projectDir)),
|
|
909
|
+
session: {
|
|
910
|
+
language: session.language,
|
|
911
|
+
project_type: session.project_type || session.project?.type,
|
|
912
|
+
execution_mode: session.execution_mode,
|
|
913
|
+
active_harness: session.active_harness,
|
|
914
|
+
},
|
|
915
|
+
status_summary: `Waiting for the user's answer to ${pending.agent}.`,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
855
918
|
if (installationV2 && session.rail_handoff_id) {
|
|
856
919
|
const completedPlanning = runtimePipeline.length;
|
|
857
920
|
return {
|
|
@@ -2405,8 +2468,8 @@ async function handleStatus(projectDir) {
|
|
|
2405
2468
|
// bracket prism-engine last computed. If the session predates v1.2
|
|
2406
2469
|
// (no context_* fields yet), the block falls back to estimateContextBracket
|
|
2407
2470
|
// so the existing response shape stays stable for consumers.
|
|
2408
|
-
const activeModel = session.active_model || null;
|
|
2409
|
-
const activeProvider = session.
|
|
2471
|
+
const activeModel = session.current_execution_binding?.model || session.active_model || null;
|
|
2472
|
+
const activeProvider = session.current_execution_binding?.provider || session.active_provider || null;
|
|
2410
2473
|
const windowTokens = session.context_window_tokens != null
|
|
2411
2474
|
? session.context_window_tokens
|
|
2412
2475
|
: resolveContextLimit(activeModel, activeProvider);
|
|
@@ -2440,9 +2503,32 @@ async function handleStatus(projectDir) {
|
|
|
2440
2503
|
},
|
|
2441
2504
|
user_level: session.user_level || 'auto',
|
|
2442
2505
|
execution_mode: session.execution_mode || 'interactive',
|
|
2506
|
+
active_harness: session.active_harness || null,
|
|
2507
|
+
current_execution_binding: session.current_execution_binding || null,
|
|
2508
|
+
pending_interaction: session.pending_interaction || null,
|
|
2443
2509
|
};
|
|
2444
2510
|
}
|
|
2445
2511
|
|
|
2512
|
+
async function handleActivateHarness(projectDir, args) {
|
|
2513
|
+
const harness = args.harness;
|
|
2514
|
+
if (!['claude', 'codex', 'grok'].includes(harness)) {
|
|
2515
|
+
return errorResult('Harness must be claude, codex, or grok.', 'INVALID_HARNESS');
|
|
2516
|
+
}
|
|
2517
|
+
const { loaded, session } = loadSession(projectDir);
|
|
2518
|
+
if (!loaded || !session) return errorResult('No session found', 'NO_SESSION');
|
|
2519
|
+
const previous = session.active_harness || null;
|
|
2520
|
+
const wasActive = session.session_status === 'active';
|
|
2521
|
+
const saved = updateSession(projectDir, { active_harness: harness, session_status: 'active' });
|
|
2522
|
+
if (!saved.saved) return errorResult(saved.error, 'SESSION_WRITE_FAILED');
|
|
2523
|
+
if (previous !== harness) {
|
|
2524
|
+
recordEvent(projectDir, EventType.HARNESS_ACTIVATED, 'orchestrator', { harness, previous_harness: previous });
|
|
2525
|
+
}
|
|
2526
|
+
if (previous !== harness || !wasActive) {
|
|
2527
|
+
recordEvent(projectDir, EventType.SESSION_STARTED, session.current_agent || 'orchestrator', { harness, resumed: Boolean(previous) });
|
|
2528
|
+
}
|
|
2529
|
+
return { activated: true, harness, previous_harness: previous, language: session.language || 'en' };
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2446
2532
|
async function handleDeviation(projectDir, args) {
|
|
2447
2533
|
const type = args.type;
|
|
2448
2534
|
const target = args.target || null;
|
|
@@ -2525,8 +2611,8 @@ async function handleExit(projectDir, args) {
|
|
|
2525
2611
|
}
|
|
2526
2612
|
|
|
2527
2613
|
// Save current state
|
|
2528
|
-
recordEvent(projectDir, EventType.SESSION_ENDED, session.current_agent || 'orchestrator', { mode: session.mode });
|
|
2529
|
-
await updateSession(projectDir, {});
|
|
2614
|
+
recordEvent(projectDir, EventType.SESSION_ENDED, session.current_agent || 'orchestrator', { mode: session.mode, harness: session.active_harness || null });
|
|
2615
|
+
await updateSession(projectDir, { session_status: 'paused' });
|
|
2530
2616
|
|
|
2531
2617
|
// Release session ownership
|
|
2532
2618
|
const userId = args['user-id'] || process.env.USER || 'default';
|
|
@@ -2536,9 +2622,14 @@ async function handleExit(projectDir, args) {
|
|
|
2536
2622
|
|
|
2537
2623
|
// Remove lock from CLAUDE.local.md
|
|
2538
2624
|
const lang = session.language || 'en';
|
|
2539
|
-
const
|
|
2625
|
+
const genericResumeMsg = RESUME_MESSAGES[lang] || RESUME_MESSAGES.en;
|
|
2626
|
+
const resumeMsg = session.active_harness === 'codex'
|
|
2627
|
+
? genericResumeMsg.replaceAll('/chati', '$chati')
|
|
2628
|
+
: genericResumeMsg;
|
|
2540
2629
|
try {
|
|
2541
|
-
|
|
2630
|
+
// Each lock target translates the generic command to its native syntax.
|
|
2631
|
+
// The returned message below remains native to the harness that exited.
|
|
2632
|
+
removeSessionLock(projectDir, genericResumeMsg);
|
|
2542
2633
|
} catch { /* expected: lock file may not exist */ }
|
|
2543
2634
|
|
|
2544
2635
|
return {
|
|
@@ -3561,6 +3652,9 @@ export async function runOrchestrate(subCommand, argv, projectDir) {
|
|
|
3561
3652
|
case 'exit':
|
|
3562
3653
|
output(await handleExit(projectDir, args));
|
|
3563
3654
|
break;
|
|
3655
|
+
case 'activate-harness':
|
|
3656
|
+
output(await handleActivateHarness(projectDir, args));
|
|
3657
|
+
break;
|
|
3564
3658
|
case 'providers':
|
|
3565
3659
|
output(await handleProviders(projectDir));
|
|
3566
3660
|
break;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
30
30
|
import { join } from 'path';
|
|
31
31
|
import { readLicenseStatus } from '../license/wait.js';
|
|
32
|
+
import * as yaml from 'js-yaml';
|
|
32
33
|
|
|
33
34
|
export const SEVERITY = Object.freeze({
|
|
34
35
|
BLOCK: 'BLOCK',
|
|
@@ -202,10 +203,13 @@ export function checkModel({ projectDir }) {
|
|
|
202
203
|
};
|
|
203
204
|
}
|
|
204
205
|
try {
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
if (!value
|
|
206
|
+
const session = yaml.load(readFileSync(sessionPath, 'utf-8')) || {};
|
|
207
|
+
const value = session.active_model;
|
|
208
|
+
const routedModel = session.current_execution_binding?.model;
|
|
209
|
+
if (!value && routedModel) {
|
|
210
|
+
return { id: 'model', severity: SEVERITY.WARN, status: 'pass', message: `Routed execution model: ${routedModel}` };
|
|
211
|
+
}
|
|
212
|
+
if (!value) {
|
|
209
213
|
return {
|
|
210
214
|
id: 'model',
|
|
211
215
|
severity: SEVERITY.WARN,
|
|
@@ -222,6 +226,70 @@ export function checkModel({ projectDir }) {
|
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
|
|
229
|
+
/** Compare canonical session state with native harness projections. */
|
|
230
|
+
export function checkRuntimeState({ projectDir }) {
|
|
231
|
+
const sessionPath = join(projectDir, '.chati', 'session.yaml');
|
|
232
|
+
if (!existsSync(sessionPath)) {
|
|
233
|
+
return { id: 'runtime-state', severity: SEVERITY.WARN, status: 'pass', message: 'Session not initialized.' };
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const session = yaml.load(readFileSync(sessionPath, 'utf-8')) || {};
|
|
237
|
+
const lockPaths = [
|
|
238
|
+
join(projectDir, 'CLAUDE.local.md'),
|
|
239
|
+
join(projectDir, 'AGENTS.override.md'),
|
|
240
|
+
join(projectDir, '.grok', 'session-lock.md'),
|
|
241
|
+
join(projectDir, '.gemini', 'session-lock.md'),
|
|
242
|
+
];
|
|
243
|
+
const projectedAgents = [];
|
|
244
|
+
for (const lockPath of lockPaths) {
|
|
245
|
+
if (!existsSync(lockPath)) continue;
|
|
246
|
+
const raw = readFileSync(lockPath, 'utf-8');
|
|
247
|
+
if (!/(?:Session Lock:\s*ACTIVE|\*\*Status:\s*ACTIVE\*\*)/i.test(raw)) continue;
|
|
248
|
+
const match = raw.match(/\*\*Agent\*\*:\s*([^\n]+)/i);
|
|
249
|
+
if (match) projectedAgents.push(match[1].trim());
|
|
250
|
+
}
|
|
251
|
+
const mismatches = [...new Set(projectedAgents)].filter(agent => agent !== session.current_agent);
|
|
252
|
+
if (mismatches.length > 0) {
|
|
253
|
+
return {
|
|
254
|
+
id: 'runtime-state', severity: SEVERITY.WARN, status: 'fail',
|
|
255
|
+
message: `Canonical current_agent (${session.current_agent || 'none'}) disagrees with native lock projection(s): ${mismatches.join(', ')}`,
|
|
256
|
+
fix: 'Run the deterministic router once from the active CLI to refresh native projections.',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return { id: 'runtime-state', severity: SEVERITY.WARN, status: 'pass', message: 'Canonical session and native lock projections agree.' };
|
|
260
|
+
} catch (err) {
|
|
261
|
+
return { id: 'runtime-state', severity: SEVERITY.WARN, status: 'fail', message: `Cannot inspect runtime state: ${err.message}` };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Surface durable ClickUp projection intents awaiting synchronization. */
|
|
266
|
+
export function checkClickUpTracking({ projectDir }) {
|
|
267
|
+
const pendingDir = join(projectDir, '.chati', 'v2', 'tracking', 'pending-projections');
|
|
268
|
+
try {
|
|
269
|
+
const durableIntents = existsSync(pendingDir)
|
|
270
|
+
? readdirSync(pendingDir).filter(name => name.endsWith('.json')).length
|
|
271
|
+
: 0;
|
|
272
|
+
const outboxPath = join(projectDir, '.chati', 'v2', 'tracking', 'clickup-outbox.json');
|
|
273
|
+
let outboxPending = 0;
|
|
274
|
+
if (existsSync(outboxPath)) {
|
|
275
|
+
const outbox = JSON.parse(readFileSync(outboxPath, 'utf8'));
|
|
276
|
+
outboxPending = (outbox.projections || []).filter(projection =>
|
|
277
|
+
['pending', 'retryable_failure', 'sent_unconfirmed'].includes(projection.delivery_state)
|
|
278
|
+
).length;
|
|
279
|
+
}
|
|
280
|
+
if (durableIntents === 0 && outboxPending === 0) {
|
|
281
|
+
return { id: 'clickup-tracking', severity: SEVERITY.WARN, status: 'pass', message: 'No pending ClickUp projections.' };
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
id: 'clickup-tracking', severity: SEVERITY.WARN, status: 'fail',
|
|
285
|
+
message: `${durableIntents} pending reconstruction intent(s) and ${outboxPending} pending ClickUp delivery projection(s) await synchronization.`,
|
|
286
|
+
fix: 'Restore ClickUp connectivity. The durable queue can then be replayed without losing local progress.',
|
|
287
|
+
};
|
|
288
|
+
} catch (err) {
|
|
289
|
+
return { id: 'clickup-tracking', severity: SEVERITY.WARN, status: 'fail', message: `Cannot inspect ClickUp projection queue: ${err.message}` };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
225
293
|
/**
|
|
226
294
|
* Node version gate. Framework assumes Node 22+ for modern ESM, fetch,
|
|
227
295
|
* globbing. Earlier versions break test runs and hook spawns.
|
|
@@ -342,6 +410,8 @@ export function runDoctor(opts) {
|
|
|
342
410
|
checkSession({ projectDir: opts.projectDir }),
|
|
343
411
|
checkHooks({ projectDir: opts.projectDir }),
|
|
344
412
|
checkModel({ projectDir: opts.projectDir }),
|
|
413
|
+
checkRuntimeState({ projectDir: opts.projectDir }),
|
|
414
|
+
checkClickUpTracking({ projectDir: opts.projectDir }),
|
|
345
415
|
checkNode(),
|
|
346
416
|
checkConstitution({ projectDir: opts.projectDir }),
|
|
347
417
|
checkFailures({ projectDir: opts.projectDir }),
|
|
@@ -106,6 +106,12 @@ const DEFAULT_SESSION = {
|
|
|
106
106
|
// context_last_bracket: last computed bracket — telemetry + /chati status.
|
|
107
107
|
active_model: null,
|
|
108
108
|
active_provider: null,
|
|
109
|
+
// The harness is the CLI/UI currently orchestrating the session. It is not
|
|
110
|
+
// the provider selected for the routed task.
|
|
111
|
+
active_harness: null,
|
|
112
|
+
session_status: 'inactive',
|
|
113
|
+
current_execution_binding: null,
|
|
114
|
+
pending_interaction: null,
|
|
109
115
|
context_tokens_used: 0,
|
|
110
116
|
context_window_tokens: null,
|
|
111
117
|
context_last_bracket: null,
|
|
@@ -237,8 +243,24 @@ export function migrateSession(session) {
|
|
|
237
243
|
});
|
|
238
244
|
}
|
|
239
245
|
|
|
246
|
+
// Seed additive runtime fields even when the schema version is current.
|
|
247
|
+
// Patch releases can therefore repair installed v1.2 sessions safely.
|
|
248
|
+
let seededRuntimeFields = false;
|
|
249
|
+
const runtimeDefaults = {
|
|
250
|
+
active_harness: null,
|
|
251
|
+
session_status: 'inactive',
|
|
252
|
+
current_execution_binding: null,
|
|
253
|
+
pending_interaction: null,
|
|
254
|
+
};
|
|
255
|
+
for (const [key, value] of Object.entries(runtimeDefaults)) {
|
|
256
|
+
if (!(key in session)) {
|
|
257
|
+
session[key] = value;
|
|
258
|
+
seededRuntimeFields = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
240
262
|
if (session.schema_version === CURRENT_SCHEMA_VERSION) {
|
|
241
|
-
return { migrated: normalizedLegacySelections, fromVersion: session.schema_version, toVersion: CURRENT_SCHEMA_VERSION };
|
|
263
|
+
return { migrated: normalizedLegacySelections || seededRuntimeFields, fromVersion: session.schema_version, toVersion: CURRENT_SCHEMA_VERSION };
|
|
242
264
|
}
|
|
243
265
|
|
|
244
266
|
// v0 (no schema_version) → v1.0 → v1.1 → v1.2
|
|
@@ -492,6 +514,67 @@ export function recordModelSelection(projectDir, selection) {
|
|
|
492
514
|
});
|
|
493
515
|
}
|
|
494
516
|
|
|
517
|
+
/** Persist an interactive question so another harness resumes it verbatim. */
|
|
518
|
+
export function recordPendingInteraction(projectDir, interaction) {
|
|
519
|
+
const sessionProjectDir = findSessionProjectDir(projectDir);
|
|
520
|
+
if (!sessionProjectDir) return { saved: false, error: 'Session file not found in working directory or its parents' };
|
|
521
|
+
if (!interaction?.agent || !interaction?.question) {
|
|
522
|
+
return { saved: false, error: 'Pending interaction requires agent and question' };
|
|
523
|
+
}
|
|
524
|
+
return mutateSession(sessionProjectDir, (session) => {
|
|
525
|
+
const previous = session.pending_interaction;
|
|
526
|
+
const taskId = interaction.taskId || `${interaction.agent}-task`;
|
|
527
|
+
const binding = {
|
|
528
|
+
provider: interaction.provider || null,
|
|
529
|
+
provider_id: interaction.providerId || null,
|
|
530
|
+
model: interaction.model || null,
|
|
531
|
+
reasoning_configuration: interaction.reasoningConfiguration || null,
|
|
532
|
+
catalog_snapshot_ref: interaction.catalogSnapshotRef || null,
|
|
533
|
+
};
|
|
534
|
+
const sameRelay = previous?.agent === interaction.agent && previous?.task_id === taskId;
|
|
535
|
+
const now = interaction.timestamp || new Date().toISOString();
|
|
536
|
+
session.current_agent = interaction.agent;
|
|
537
|
+
session.current_execution_binding = binding;
|
|
538
|
+
session.agents = session.agents || {};
|
|
539
|
+
session.agents[interaction.agent] = {
|
|
540
|
+
...(session.agents[interaction.agent] || {}),
|
|
541
|
+
status: 'waiting_for_user',
|
|
542
|
+
};
|
|
543
|
+
session.pending_interaction = {
|
|
544
|
+
status: 'waiting_for_user',
|
|
545
|
+
agent: interaction.agent,
|
|
546
|
+
task_id: taskId,
|
|
547
|
+
language: session.language || 'en',
|
|
548
|
+
question: interaction.question,
|
|
549
|
+
relay_cycles: sameRelay ? (previous.relay_cycles || 1) + 1 : 1,
|
|
550
|
+
execution_binding: binding,
|
|
551
|
+
created_at: sameRelay ? previous.created_at : now,
|
|
552
|
+
updated_at: now,
|
|
553
|
+
};
|
|
554
|
+
return session;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** Clear a pending interaction only when the caller owns that relay. */
|
|
559
|
+
export function clearPendingInteraction(projectDir, match = {}) {
|
|
560
|
+
const sessionProjectDir = findSessionProjectDir(projectDir);
|
|
561
|
+
if (!sessionProjectDir) return { saved: false, cleared: false, error: 'Session file not found in working directory or its parents' };
|
|
562
|
+
let cleared = false;
|
|
563
|
+
const result = mutateSession(sessionProjectDir, (session) => {
|
|
564
|
+
const pending = session.pending_interaction;
|
|
565
|
+
if (!pending) return session;
|
|
566
|
+
if (match.agent && pending.agent !== match.agent) return session;
|
|
567
|
+
if (match.taskId && pending.task_id !== match.taskId) return session;
|
|
568
|
+
session.pending_interaction = null;
|
|
569
|
+
if (session.agents?.[pending.agent]?.status === 'waiting_for_user') {
|
|
570
|
+
session.agents[pending.agent].status = 'in_progress';
|
|
571
|
+
}
|
|
572
|
+
cleared = true;
|
|
573
|
+
return session;
|
|
574
|
+
});
|
|
575
|
+
return { ...result, cleared };
|
|
576
|
+
}
|
|
577
|
+
|
|
495
578
|
/**
|
|
496
579
|
* Record a mode transition in session history.
|
|
497
580
|
* @param {string} projectDir
|
|
@@ -392,6 +392,7 @@ function buildSessionSection(config, resolvedModelInfo = {}) {
|
|
|
392
392
|
`- **Type**: ${project.type || 'greenfield'}`,
|
|
393
393
|
`- **Mode**: ${project.state || 'discover'}`,
|
|
394
394
|
`- **Language**: ${state.language || 'en'} (interaction) / English (artifacts)`,
|
|
395
|
+
`- **Language Contract**: Every visible interaction, including your first response, preface, progress update, question, error, pause and resumption, MUST use ${state.language || 'en'}.`,
|
|
395
396
|
`- **User Level**: ${state.user_level || 'auto'}`,
|
|
396
397
|
`- **Execution Mode**: ${state.execution_mode || 'autonomous'}`,
|
|
397
398
|
`- **Your Agent**: ${config.agent}`,
|
|
@@ -23,6 +23,8 @@ import { getRateLimiter } from './rate-limiter.js';
|
|
|
23
23
|
import { initCollector, track as telemetryTrack, flush as telemetryFlush } from '../telemetry/collector.js';
|
|
24
24
|
import { sendEvents } from '../telemetry/sender.js';
|
|
25
25
|
import { getTelemetryConfig, isEnabled as isTelemetryEnabled } from '../telemetry/config.js';
|
|
26
|
+
import { recordPendingInteraction, clearPendingInteraction } from '../orchestrator/session-manager.js';
|
|
27
|
+
import { recordEvent, EventType } from '../intelligence/timeline.js';
|
|
26
28
|
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
28
30
|
// CLI argument parsing (no external deps)
|
|
@@ -111,6 +113,11 @@ async function main() {
|
|
|
111
113
|
}
|
|
112
114
|
const readiness = checkProviderReadiness(spawnProvider, { workingDir: projectDir });
|
|
113
115
|
if (!readiness.ready) {
|
|
116
|
+
const relayState = clearPendingForFailedRelay(projectDir, args);
|
|
117
|
+
if (!relayState.saved) {
|
|
118
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: readiness.code });
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
114
121
|
outputResult({ status: 'error', error: readiness.detail, code: readiness.code, provider: spawnProvider });
|
|
115
122
|
process.exit(1);
|
|
116
123
|
}
|
|
@@ -154,6 +161,11 @@ async function main() {
|
|
|
154
161
|
},
|
|
155
162
|
});
|
|
156
163
|
} catch (err) {
|
|
164
|
+
const relayState = clearPendingForFailedRelay(projectDir, args);
|
|
165
|
+
if (!relayState.saved) {
|
|
166
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: err.message });
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
157
169
|
outputError(`Failed to spawn terminal: ${err.message}`);
|
|
158
170
|
process.exit(1);
|
|
159
171
|
}
|
|
@@ -213,8 +225,14 @@ async function main() {
|
|
|
213
225
|
|
|
214
226
|
// Parse the handoff from stdout
|
|
215
227
|
const parsed = parseAgentOutput(stdout);
|
|
228
|
+
const recovered = recoverInteractiveHandoff(args.agent, stdout, handle.exitCode);
|
|
216
229
|
|
|
217
230
|
if (handle.exitCode !== 0 && /not logged in|not authenticated/i.test(providerOutput)) {
|
|
231
|
+
const relayState = clearPendingForFailedRelay(projectDir, args);
|
|
232
|
+
if (!relayState.saved) {
|
|
233
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: 'PROVIDER_AUTH_STATE_MISMATCH' });
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
218
236
|
outputResult({
|
|
219
237
|
status: 'error',
|
|
220
238
|
code: 'PROVIDER_AUTH_STATE_MISMATCH',
|
|
@@ -227,6 +245,11 @@ async function main() {
|
|
|
227
245
|
}
|
|
228
246
|
|
|
229
247
|
if (parsed.found && parsed.valid) {
|
|
248
|
+
const persistence = persistInteractiveState(projectDir, args, parsed.handoff, promptResult.model, spawnProvider);
|
|
249
|
+
if (!persistence.saved) {
|
|
250
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: persistence.error, agent: args.agent });
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
230
253
|
outputResult({
|
|
231
254
|
status: parsed.handoff.status,
|
|
232
255
|
agent: args.agent,
|
|
@@ -238,17 +261,22 @@ async function main() {
|
|
|
238
261
|
elapsed,
|
|
239
262
|
costEstimate,
|
|
240
263
|
});
|
|
241
|
-
} else if (
|
|
264
|
+
} else if (recovered) {
|
|
242
265
|
// Interactive discovery models sometimes answer with the user-facing
|
|
243
266
|
// question but omit the machine block. Preserve the exact question while
|
|
244
267
|
// restoring the deterministic relay contract for the parent orchestrator.
|
|
268
|
+
const persistence = persistInteractiveState(projectDir, args, recovered, promptResult.model, spawnProvider);
|
|
269
|
+
if (!persistence.saved) {
|
|
270
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: persistence.error, agent: args.agent });
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
245
273
|
outputResult({
|
|
246
274
|
status: 'needs_input',
|
|
247
275
|
agent: args.agent,
|
|
248
276
|
model: promptResult.model,
|
|
249
277
|
provider: spawnProvider,
|
|
250
278
|
exitCode: handle.exitCode,
|
|
251
|
-
handoff:
|
|
279
|
+
handoff: recovered,
|
|
252
280
|
contractRecovery: 'interactive_output_wrapped',
|
|
253
281
|
elapsed,
|
|
254
282
|
costEstimate,
|
|
@@ -256,6 +284,11 @@ async function main() {
|
|
|
256
284
|
} else {
|
|
257
285
|
// A non-interactive agent that omits the handoff violated the execution
|
|
258
286
|
// contract and cannot be treated as successful or partially complete.
|
|
287
|
+
const relayState = clearPendingForFailedRelay(projectDir, args);
|
|
288
|
+
if (!relayState.saved) {
|
|
289
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: parsed.found ? 'INVALID_HANDOFF' : 'MISSING_HANDOFF' });
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
259
292
|
outputResult({
|
|
260
293
|
status: 'error',
|
|
261
294
|
code: parsed.found ? 'INVALID_HANDOFF' : 'MISSING_HANDOFF',
|
|
@@ -275,6 +308,31 @@ async function main() {
|
|
|
275
308
|
process.exit(handle.exitCode === 0 ? 0 : 1);
|
|
276
309
|
}
|
|
277
310
|
|
|
311
|
+
function persistInteractiveState(projectDir, args, handoff, model, provider) {
|
|
312
|
+
if (handoff?.status === 'needs_input' && handoff.needs_input_question) {
|
|
313
|
+
const result = recordPendingInteraction(projectDir, {
|
|
314
|
+
agent: args.agent,
|
|
315
|
+
taskId: args['task-id'],
|
|
316
|
+
question: handoff.needs_input_question,
|
|
317
|
+
provider,
|
|
318
|
+
providerId: args['provider-id'] || null,
|
|
319
|
+
model,
|
|
320
|
+
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
321
|
+
catalogSnapshotRef: args['catalog-snapshot-ref'] || null,
|
|
322
|
+
});
|
|
323
|
+
recordEvent(projectDir, EventType.AGENT_WAITING_FOR_INPUT, args.agent, {
|
|
324
|
+
task_id: args['task-id'],
|
|
325
|
+
});
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
return clearPendingInteraction(projectDir, { agent: args.agent, taskId: args['task-id'] });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function clearPendingForFailedRelay(projectDir, args) {
|
|
332
|
+
if (!args?.['additional-context']) return { saved: true, cleared: false };
|
|
333
|
+
return clearPendingInteraction(projectDir, { agent: args.agent, taskId: args['task-id'] });
|
|
334
|
+
}
|
|
335
|
+
|
|
278
336
|
// ---------------------------------------------------------------------------
|
|
279
337
|
// Helpers
|
|
280
338
|
// ---------------------------------------------------------------------------
|
|
@@ -171,6 +171,10 @@ export const SESSION_SCHEMA = {
|
|
|
171
171
|
language: { type: 'string', required: true, default: 'en' },
|
|
172
172
|
pipeline_phase: { type: 'string', enum: ['discover', 'plan', 'rail', 'release', 'build', 'validate', 'deploy', 'completed'] },
|
|
173
173
|
current_agent: { type: 'string' },
|
|
174
|
+
active_harness: { type: 'string', enum: ['claude', 'codex', 'grok'] },
|
|
175
|
+
session_status: { type: 'string', enum: ['inactive', 'active', 'paused', 'completed'] },
|
|
176
|
+
current_execution_binding: { type: 'object' },
|
|
177
|
+
pending_interaction: { type: 'object' },
|
|
174
178
|
governance_mode: { type: 'string', enum: ['planning', 'build', 'deploy'] },
|
|
175
179
|
execution_mode: { type: 'string', enum: ['interactive', 'autonomous'] },
|
|
176
180
|
context_bracket: { type: 'string', enum: ['FRESH', 'MODERATE', 'DEPLETED', 'CRITICAL'] },
|
|
@@ -191,6 +195,7 @@ export const CONFIG_SCHEMA = {
|
|
|
191
195
|
language: { type: 'string', default: 'en' },
|
|
192
196
|
ides: { type: 'array' },
|
|
193
197
|
providers: { type: 'object' },
|
|
198
|
+
integrations: { type: 'object' },
|
|
194
199
|
agent_overrides: { type: 'object' },
|
|
195
200
|
},
|
|
196
201
|
};
|
package/src/wizard/feedback.js
CHANGED
|
@@ -13,6 +13,17 @@ export function createSpinner(text) {
|
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/** Run one automatic operation with a spinner that always stops. */
|
|
17
|
+
export async function withSpinner(text, operation, { spinnerFactory = createSpinner } = {}) {
|
|
18
|
+
const spinner = spinnerFactory(text);
|
|
19
|
+
spinner.start();
|
|
20
|
+
try {
|
|
21
|
+
return await operation();
|
|
22
|
+
} finally {
|
|
23
|
+
spinner.stop();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/**
|
|
17
28
|
* Show installation progress step
|
|
18
29
|
*/
|
package/src/wizard/i18n.js
CHANGED
|
@@ -106,6 +106,9 @@ const FALLBACK_EN = {
|
|
|
106
106
|
system_prerequisites_failed: 'System prerequisites not met:',
|
|
107
107
|
fix_prerequisites: 'Fix the issues above and try again.',
|
|
108
108
|
system_check: 'System check:',
|
|
109
|
+
system_checking: 'Checking system requirements...',
|
|
110
|
+
catalog_resolving: 'Loading the signed model catalog...',
|
|
111
|
+
internal_authorizing: 'Verifying access to Internal mode...',
|
|
109
112
|
created_provider_claude: 'Created .claude/commands/ (thin router)',
|
|
110
113
|
created_provider_gemini: 'Created .gemini/commands/ (TOML command)',
|
|
111
114
|
created_provider_codex: 'Created .agents/skills/chati/ (Codex skill)',
|
|
@@ -120,6 +123,7 @@ const FALLBACK_EN = {
|
|
|
120
123
|
license_placeholder: 'CHATI-XXXX-XXXX-XXXX',
|
|
121
124
|
license_skipped: 'Skipped. Run: npx chati-dev activate --key=YOUR-KEY when ready.',
|
|
122
125
|
license_activating: 'Activating license...',
|
|
126
|
+
license_verifying: 'Verifying saved license...',
|
|
123
127
|
license_activated: 'License activated! Plan: {plan}, {days} day(s) remaining.',
|
|
124
128
|
license_activation_failed: 'Activation failed: {error}',
|
|
125
129
|
license_retry: 'Run: npx chati-dev activate --key=YOUR-KEY to try again.',
|
package/src/wizard/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join, dirname, basename } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { logBanner } from '../utils/logger.js';
|
|
6
6
|
import { WIZARD_BACK, stepLanguage, stepProjectType, stepProviderSelection, stepInstallationMode, stepConfirmation, stepTermsOfUse } from './questions.js';
|
|
7
|
-
import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
|
|
7
|
+
import { createSpinner, withSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
|
|
8
8
|
import { installFramework } from '../installer/core.js';
|
|
9
9
|
import { INSTALLATION_ARTIFACT_PATH, dryRunV2, installV2, reconfigureV2 } from '../installer-v2/index.js';
|
|
10
10
|
import { buildWizardV2InstallationInput } from '../installer-v2/wizard-installation.js';
|
|
@@ -48,7 +48,7 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
48
48
|
|
|
49
49
|
// Preflight: validate system prerequisites (Node >= 20, npm, Git, Playwright)
|
|
50
50
|
const { runPreflightCheck } = await import('../installer/preflight.js');
|
|
51
|
-
const preflight = await runPreflightCheck();
|
|
51
|
+
const preflight = await withSpinner(t('installer.system_checking'), () => runPreflightCheck());
|
|
52
52
|
const preflightFmt = preflight.checks.map(c => {
|
|
53
53
|
const icon = c.status === 'pass' ? '✓' : c.status === 'warn' ? '⚠' : '✗';
|
|
54
54
|
return ` ${icon} ${c.message}`;
|
|
@@ -128,9 +128,13 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
128
128
|
continue;
|
|
129
129
|
}
|
|
130
130
|
selectedProviders = value;
|
|
131
|
-
catalogResolution
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
if (!catalogResolution) {
|
|
132
|
+
catalogResolution = await withSpinner(t('installer.catalog_resolving'), async () => (
|
|
133
|
+
options.signedCapabilityCatalogEnvelope
|
|
134
|
+
? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
|
|
135
|
+
: resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch })
|
|
136
|
+
));
|
|
137
|
+
}
|
|
134
138
|
// Provider selection defines the permitted routing pool. Model discovery
|
|
135
139
|
// and task-specific choice stay inside the control plane.
|
|
136
140
|
modelSelections = options.modelSelections;
|
|
@@ -149,7 +153,10 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
149
153
|
installationMode = value;
|
|
150
154
|
if (installationMode === 'internal') {
|
|
151
155
|
try {
|
|
152
|
-
await
|
|
156
|
+
await withSpinner(
|
|
157
|
+
t('installer.internal_authorizing'),
|
|
158
|
+
() => requireLicenseEntitlement('internal', { validate: options.licenseValidator }),
|
|
159
|
+
);
|
|
153
160
|
} catch (error) {
|
|
154
161
|
if (options.installationMode !== undefined) throw error;
|
|
155
162
|
p.log.error(error.message);
|
|
@@ -328,7 +335,10 @@ async function runLicenseActivationStep() {
|
|
|
328
335
|
const existingKey = getLicenseKey();
|
|
329
336
|
if (existingKey) {
|
|
330
337
|
try {
|
|
331
|
-
const status = await
|
|
338
|
+
const status = await withSpinner(
|
|
339
|
+
t('installer.license_verifying'),
|
|
340
|
+
() => ensureCurrentMachineLicense({ key: existingKey }),
|
|
341
|
+
);
|
|
332
342
|
if (status.status === 'VALID') return;
|
|
333
343
|
p.log.warn(t('installer.license_saved_invalid', { reason: status.reason || status.status }));
|
|
334
344
|
} catch (error) {
|