chati-dev 4.4.1 → 4.5.2
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/README.md +29 -27
- package/bin/chati.js +235 -1
- package/framework/agents/plan/tasks.md +46 -1
- package/framework/config.yaml +2 -2
- package/framework/constitution.md +13 -16
- package/framework/context/governance.md +5 -5
- package/framework/context/root.md +5 -5
- package/framework/i18n/en.yaml +8 -2
- package/framework/i18n/es.yaml +8 -2
- package/framework/i18n/fr.yaml +8 -2
- package/framework/i18n/pt.yaml +8 -2
- package/framework/manifest.json +21 -21
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati.md +43 -12
- package/node_modules/@chati/browser-capability/README.md +10 -0
- package/node_modules/@chati/browser-capability/package.json +17 -0
- package/node_modules/@chati/browser-capability/src/index.js +165 -0
- package/node_modules/@chati/core/package.json +13 -0
- package/node_modules/@chati/core/src/index.js +111 -0
- package/node_modules/@chati/knowledge-context/package.json +17 -0
- package/node_modules/@chati/knowledge-context/src/index.js +202 -0
- package/node_modules/@chati/planning/package.json +17 -0
- package/node_modules/@chati/planning/src/index.js +367 -0
- package/node_modules/@chati/provider-registry/package.json +16 -0
- package/node_modules/@chati/provider-registry/src/index.js +148 -0
- package/node_modules/@chati/rail/README.md +24 -0
- package/node_modules/@chati/rail/package.json +19 -0
- package/node_modules/@chati/rail/src/index.js +437 -0
- package/node_modules/@chati/release-lane/README.md +24 -0
- package/node_modules/@chati/release-lane/package.json +17 -0
- package/node_modules/@chati/release-lane/src/index.js +172 -0
- package/node_modules/@chati/review-council/package.json +17 -0
- package/node_modules/@chati/review-council/src/index.js +264 -0
- package/node_modules/@chati/tracking-clickup/README.md +55 -0
- package/node_modules/@chati/tracking-clickup/package.json +17 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +293 -0
- package/package.json +25 -3
- package/src/config/ide-configs.js +11 -0
- package/src/context/domain-loader.js +1 -1
- package/src/dashboard/data-reader.js +1 -1
- package/src/executors/runner.js +1 -1
- package/src/installer/core.js +14 -13
- package/src/installer/provider-overlay.js +8 -15
- package/src/installer/scaffold-applier.js +1 -1
- package/src/installer/templates.js +12 -25
- package/src/installer/validator.js +5 -10
- package/src/installer-v2/catalog-client.js +165 -0
- package/src/installer-v2/index.js +304 -0
- package/src/installer-v2/model-catalog-envelope.json +278 -0
- package/src/installer-v2/model-catalog.json +130 -0
- package/src/installer-v2/model-catalog.sig +1 -0
- package/src/installer-v2/wizard-installation.js +116 -0
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +27 -1
- package/src/memory/session-digest.js +1 -1
- package/src/merger/yaml-merger.js +1 -1
- package/src/orchestrator/browser-runtime.js +25 -0
- package/src/orchestrator/cli.js +93 -8
- package/src/orchestrator/clickup-projection.js +84 -0
- package/src/orchestrator/clickup-runtime.js +13 -0
- package/src/orchestrator/knowledge-runtime.js +64 -0
- package/src/orchestrator/planning-runtime.js +127 -0
- package/src/orchestrator/rail-runtime.js +421 -0
- package/src/orchestrator/release-runtime.js +14 -0
- package/src/orchestrator/review-runtime.js +74 -0
- package/src/orchestrator/runtime-installation-v2.js +57 -0
- package/src/orchestrator/session-manager.js +1 -1
- package/src/telemetry/config.js +1 -1
- package/src/terminal/adapters/grok-adapter.js +16 -0
- package/src/terminal/adapters/index.js +1 -0
- package/src/terminal/cli-registry.js +20 -5
- package/src/terminal/prompt-builder.js +4 -2
- package/src/terminal/run-agent.js +3 -0
- package/src/terminal/run-parallel.js +21 -10
- package/src/terminal/spawner.js +7 -1
- package/src/terminal/team-task-list.js +1 -1
- package/src/upgrade/checker.js +1 -1
- package/src/upgrade/migrator.js +1 -1
- package/src/utils/config-parser.js +3 -8
- package/src/wizard/i18n.js +10 -4
- package/src/wizard/index.js +49 -17
- package/src/wizard/questions.js +50 -28
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { ContractError, canonicalize, sha256 } from '@chati/core';
|
|
4
|
+
|
|
5
|
+
const RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
6
|
+
const DELIVERY_STATES = new Set(['pending', 'sent_unconfirmed', 'confirmed', 'retryable_failure', 'permanent_failure']);
|
|
7
|
+
const RECONCILIATION_STATES = new Set(['not_due', 'pending', 'reconciled', 'drift_detected', 'blocked']);
|
|
8
|
+
const OPERATIONS = new Set(['create', 'update', 'close']);
|
|
9
|
+
export const HOURLY_WATCHER_INTERVAL_MS = 60 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
function fail(code, message, details = {}) { throw new ContractError(code, message, details); }
|
|
12
|
+
function string(value, code, label) { if (typeof value !== 'string' || value.trim() === '') fail(code, `${label} must be a non-empty string`); }
|
|
13
|
+
function object(value, code, label) { if (!value || typeof value !== 'object' || Array.isArray(value)) fail(code, `${label} must be an object`); }
|
|
14
|
+
function timestamp(clock) {
|
|
15
|
+
if (typeof clock !== 'function') fail('MISSING_CLOCK', 'a deterministic clock function is required');
|
|
16
|
+
const result = clock();
|
|
17
|
+
const value = result instanceof Date ? result.toISOString() : result;
|
|
18
|
+
if (typeof value !== 'string' || !RFC3339.test(value) || Number.isNaN(Date.parse(value))) fail('INVALID_CLOCK', 'clock must return a valid RFC3339 timestamp or Date');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function writeAtomic(path, data) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
24
|
+
writeFileSync(temporary, `${JSON.stringify(canonicalize(data), null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
25
|
+
renameSync(temporary, path);
|
|
26
|
+
}
|
|
27
|
+
function readOutbox(path) {
|
|
28
|
+
if (!existsSync(path)) return { schema_version: 1, projections: [] };
|
|
29
|
+
let data;
|
|
30
|
+
try { data = JSON.parse(readFileSync(path, 'utf8')); } catch { fail('OUTBOX_CORRUPT', 'outbox JSON is malformed'); }
|
|
31
|
+
if (data?.schema_version !== 1 || !Array.isArray(data.projections)) fail('OUTBOX_CORRUPT', 'outbox schema is invalid');
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
function closureMutation(payload) {
|
|
35
|
+
const status = typeof payload.status === 'string' ? payload.status.trim().toLowerCase() : '';
|
|
36
|
+
return ['closed', 'close', 'done', 'complete', 'completed'].includes(status)
|
|
37
|
+
|| Object.hasOwn(payload, 'end_date') || Object.hasOwn(payload, 'endDate');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* C-07 local projection authority. It intentionally has no ClickUp SDK,
|
|
42
|
+
* credentials, network client, timer or knowledge-store integration. A caller
|
|
43
|
+
* injects a transport only after an independently authorized boundary.
|
|
44
|
+
*/
|
|
45
|
+
export class TrackingClickUp {
|
|
46
|
+
#outboxPath;
|
|
47
|
+
#evolutionDir;
|
|
48
|
+
#clock;
|
|
49
|
+
#transport;
|
|
50
|
+
#verifyCompletion;
|
|
51
|
+
#verifyReference;
|
|
52
|
+
#resolveReference;
|
|
53
|
+
#preflight;
|
|
54
|
+
#watcherIntervalMs;
|
|
55
|
+
#state;
|
|
56
|
+
|
|
57
|
+
constructor({ outbox_path, project_evolution_dir, clock, transport = null, verify_completion, verify_reference, resolve_reference, preflight = null, watcher_interval_ms = HOURLY_WATCHER_INTERVAL_MS } = {}) {
|
|
58
|
+
string(outbox_path, 'INVALID_OUTBOX_PATH', 'outbox_path');
|
|
59
|
+
string(project_evolution_dir, 'INVALID_EVOLUTION_DIR', 'project_evolution_dir');
|
|
60
|
+
if (transport !== null && (!transport || typeof transport.send !== 'function')) fail('INVALID_TRANSPORT', 'transport.send must be a function');
|
|
61
|
+
if (typeof verify_completion !== 'function') fail('MISSING_COMPLETION_VERIFIER', 'verify_completion must be an injected C-06 verifier');
|
|
62
|
+
if (typeof verify_reference !== 'function') fail('MISSING_REFERENCE_VERIFIER', 'verify_reference must verify immutable handoff and journal references');
|
|
63
|
+
if (typeof resolve_reference !== 'function') fail('MISSING_REFERENCE_RESOLVER', 'resolve_reference must produce verified handoff and journal views');
|
|
64
|
+
if (preflight !== null && (typeof preflight.validate_schema !== 'function' || typeof preflight.authorize_transport !== 'function')) fail('INVALID_PREFLIGHT', 'preflight requires validate_schema and authorize_transport functions');
|
|
65
|
+
if (watcher_interval_ms !== HOURLY_WATCHER_INTERVAL_MS) fail('INVALID_WATCHER_INTERVAL', 'watcher interval must be exactly hourly');
|
|
66
|
+
this.#outboxPath = outbox_path;
|
|
67
|
+
this.#evolutionDir = project_evolution_dir;
|
|
68
|
+
this.#clock = clock;
|
|
69
|
+
this.#transport = transport;
|
|
70
|
+
this.#verifyCompletion = verify_completion;
|
|
71
|
+
this.#verifyReference = verify_reference;
|
|
72
|
+
this.#resolveReference = resolve_reference;
|
|
73
|
+
this.#preflight = preflight;
|
|
74
|
+
this.#watcherIntervalMs = watcher_interval_ms;
|
|
75
|
+
this.#state = readOutbox(outbox_path);
|
|
76
|
+
// Project Evolution is a derivative, never the only copy of evidence.
|
|
77
|
+
// Rebuild it after an interrupted outbox/evolution write sequence.
|
|
78
|
+
for (const projectId of new Set(this.#state.projections.map((item) => item.project_id))) this.#evolve(projectId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get projections() { return Object.freeze(this.#state.projections.map((item) => Object.freeze(canonicalize(item)))); }
|
|
82
|
+
get outboxPath() { return this.#outboxPath; }
|
|
83
|
+
get watcherConfig() { return Object.freeze({ interval_ms: this.#watcherIntervalMs, mode: 'external-one-shot-scheduler' }); }
|
|
84
|
+
|
|
85
|
+
#persist() { writeAtomic(this.#outboxPath, this.#state); }
|
|
86
|
+
#find(projectionId) {
|
|
87
|
+
string(projectionId, 'INVALID_PROJECTION_ID', 'projection_id');
|
|
88
|
+
const projection = this.#state.projections.find((item) => item.projection_id === projectionId);
|
|
89
|
+
if (!projection) fail('PROJECTION_NOT_FOUND', 'projection does not exist', { projection_id: projectionId });
|
|
90
|
+
return projection;
|
|
91
|
+
}
|
|
92
|
+
#evolve(projectId) {
|
|
93
|
+
const relevant = this.#state.projections.filter((item) => item.project_id === projectId);
|
|
94
|
+
const latestByTask = new Map();
|
|
95
|
+
for (const item of relevant) latestByTask.set(item.task_id, item);
|
|
96
|
+
const sourceProjections = relevant.map(({ project_evolution_ref, ...source }) => source);
|
|
97
|
+
const snapshot = canonicalize({
|
|
98
|
+
schema_version: 1,
|
|
99
|
+
project_id: projectId,
|
|
100
|
+
current_phase: [...latestByTask.values()].at(-1)?.handoff_view.phase ?? 'unknown',
|
|
101
|
+
tasks: [...latestByTask.values()].map((item) => ({ task_id: item.task_id, attempt_id: item.attempt_id, phase: item.handoff_view.phase, canonical_state: item.journal_view.task_state, blockers: item.journal_view.blockers, delivery_state: item.delivery_state, reconciliation_state: item.reconciliation_state })),
|
|
102
|
+
decisions: [...new Map(relevant.flatMap((item) => item.decisions.map((decision) => [decision.decision_ref, decision]))).values()],
|
|
103
|
+
verified_handoff_refs: [...new Set(relevant.map((item) => item.handoff_ref))],
|
|
104
|
+
verified_journal_refs: [...new Set(relevant.map((item) => item.journal_ref))],
|
|
105
|
+
receipt_refs: [...new Set(relevant.map((item) => item.receipt_ref).filter(Boolean))],
|
|
106
|
+
blockers: relevant.filter((item) => item.delivery_state === 'permanent_failure' || item.reconciliation_state === 'blocked').map((item) => item.projection_id),
|
|
107
|
+
projection_backlog: relevant.filter((item) => !['confirmed', 'permanent_failure'].includes(item.delivery_state)).map((item) => item.projection_id),
|
|
108
|
+
source_outbox_hash: sha256({ schema_version: 1, projections: sourceProjections }),
|
|
109
|
+
});
|
|
110
|
+
const digest = sha256(snapshot);
|
|
111
|
+
const path = join(this.#evolutionDir, projectId, `${digest}.json`);
|
|
112
|
+
writeAtomic(path, snapshot);
|
|
113
|
+
return Object.freeze({ project_evolution_ref: `project-evolution://${projectId}/${digest}`, path, digest });
|
|
114
|
+
}
|
|
115
|
+
#setEvolution(projection) {
|
|
116
|
+
const evolution = this.#evolve(projection.project_id);
|
|
117
|
+
projection.project_evolution_ref = evolution.project_evolution_ref;
|
|
118
|
+
return evolution;
|
|
119
|
+
}
|
|
120
|
+
#preflightTransport(projection) {
|
|
121
|
+
validateProjection(projection);
|
|
122
|
+
if (!this.#preflight) fail('TRANSPORT_PRECONDITION_UNMET', 'schema and authorization preflight are required before any transport call');
|
|
123
|
+
if (this.#preflight.validate_schema(Object.freeze(canonicalize(projection))) !== true) fail('SCHEMA_NOT_AUTHORIZED', 'remote schema preflight rejected projection');
|
|
124
|
+
if (this.#preflight.authorize_transport(Object.freeze(canonicalize(projection))) !== true) fail('TRANSPORT_NOT_AUTHORIZED', 'transport authorization or token gate rejected projection');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
enqueue(input = {}) {
|
|
128
|
+
object(input, 'INVALID_PROJECTION', 'projection');
|
|
129
|
+
const { projection_id, idempotency_key, canonical_event_id, task_id, clickup_ref, operation, payload, project_id, handoff_ref, journal_ref } = input;
|
|
130
|
+
const attempt_id = input.attempt_id ?? `project:${project_id}:no-attempt`;
|
|
131
|
+
for (const [name, value] of Object.entries({ projection_id, idempotency_key, canonical_event_id, task_id, clickup_ref, project_id })) string(value, 'INVALID_PROJECTION', name);
|
|
132
|
+
string(attempt_id, 'INVALID_PROJECTION', 'attempt_id');
|
|
133
|
+
if (!OPERATIONS.has(operation)) fail('INVALID_PROJECTION_OPERATION', 'operation must be create, update, or close');
|
|
134
|
+
object(payload, 'INVALID_PROJECTION', 'payload');
|
|
135
|
+
string(handoff_ref, 'MISSING_SOURCE_REFERENCE', 'handoff_ref');
|
|
136
|
+
string(journal_ref, 'MISSING_SOURCE_REFERENCE', 'journal_ref');
|
|
137
|
+
if (this.#verifyReference(handoff_ref, 'handoff') !== true || this.#verifyReference(journal_ref, 'journal') !== true) fail('UNVERIFIED_SOURCE_REFERENCE', 'handoff and journal references must be verified before projection');
|
|
138
|
+
const handoff_view = this.#resolveReference(handoff_ref, 'handoff');
|
|
139
|
+
const journal_view = this.#resolveReference(journal_ref, 'journal');
|
|
140
|
+
object(handoff_view, 'INVALID_HANDOFF_VIEW', 'resolved handoff view');
|
|
141
|
+
object(journal_view, 'INVALID_JOURNAL_VIEW', 'resolved journal view');
|
|
142
|
+
string(handoff_view.phase, 'INVALID_HANDOFF_VIEW', 'resolved handoff view.phase');
|
|
143
|
+
string(journal_view.task_state, 'INVALID_JOURNAL_VIEW', 'resolved journal view.task_state');
|
|
144
|
+
if (!Array.isArray(journal_view.blockers) || !journal_view.blockers.every((item) => typeof item === 'string' && item)) fail('INVALID_JOURNAL_VIEW', 'resolved journal view.blockers must be non-empty strings');
|
|
145
|
+
if (input.decisions !== undefined && (!Array.isArray(input.decisions) || !input.decisions.every((item) => item && typeof item === 'object' && typeof item.decision_ref === 'string' && item.decision_ref && this.#verifyReference(item.decision_ref, 'decision') === true))) fail('INVALID_DECISIONS', 'decisions must be verified immutable project decision references');
|
|
146
|
+
const duplicateByKey = this.#state.projections.find((item) => item.idempotency_key === idempotency_key);
|
|
147
|
+
const duplicateById = this.#state.projections.find((item) => item.projection_id === projection_id);
|
|
148
|
+
const duplicate = duplicateByKey || duplicateById;
|
|
149
|
+
if (duplicate) {
|
|
150
|
+
const sameIdentity = duplicateByKey === duplicateById
|
|
151
|
+
&& duplicate.task_id === task_id
|
|
152
|
+
&& duplicate.attempt_id === attempt_id
|
|
153
|
+
&& duplicate.operation === operation
|
|
154
|
+
&& duplicate.payload_hash === sha256(payload);
|
|
155
|
+
if (sameIdentity) return Object.freeze(canonicalize(duplicate));
|
|
156
|
+
if (duplicateByKey) fail('DUPLICATE_IDEMPOTENCY_KEY', 'idempotency_key already exists with different material');
|
|
157
|
+
fail('DUPLICATE_PROJECTION_ID', 'projection_id already exists with different material');
|
|
158
|
+
}
|
|
159
|
+
if (operation === 'close' || closureMutation(payload)) {
|
|
160
|
+
object(input.completion, 'MISSING_COMPLETION_EVIDENCE', 'completion');
|
|
161
|
+
const verdict = this.#verifyCompletion(input.completion);
|
|
162
|
+
if (verdict !== true) fail('INVALID_COMPLETION_EVIDENCE', 'close requires a valid C-06 completion');
|
|
163
|
+
}
|
|
164
|
+
const projection = canonicalize({
|
|
165
|
+
schema_version: 1, projection_id, idempotency_key, canonical_event_id, task_id, attempt_id, clickup_ref, project_id, handoff_ref, journal_ref, handoff_view: canonicalize(handoff_view), journal_view: canonicalize(journal_view), decisions: canonicalize(input.decisions ?? []), operation,
|
|
166
|
+
payload_hash: sha256(payload), payload, delivery_state: 'pending', reconciliation_state: 'not_due', created_at: timestamp(this.#clock), attempts: 0,
|
|
167
|
+
});
|
|
168
|
+
this.#state.projections.push(projection);
|
|
169
|
+
this.#persist(); // Durable before any injected transport is called.
|
|
170
|
+
this.#setEvolution(projection);
|
|
171
|
+
this.#persist();
|
|
172
|
+
return Object.freeze(canonicalize(projection));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
deliver({ projection_id } = {}) {
|
|
176
|
+
const projection = this.#find(projection_id);
|
|
177
|
+
if (!this.#transport) fail('TRANSPORT_NOT_CONFIGURED', 'no authorized projection transport is configured');
|
|
178
|
+
if (projection.delivery_state === 'confirmed') return Object.freeze(canonicalize(projection));
|
|
179
|
+
if (projection.delivery_state === 'permanent_failure') fail('PERMANENT_FAILURE', 'permanently failed projection cannot be retried');
|
|
180
|
+
if (projection.delivery_state === 'sent_unconfirmed') fail('UNCONFIRMED_DELIVERY_REQUIRES_LOOKUP', 'unconfirmed delivery must be resolved by idempotency lookup before resend');
|
|
181
|
+
this.#preflightTransport(projection);
|
|
182
|
+
projection.attempts += 1;
|
|
183
|
+
projection.last_attempt_at = timestamp(this.#clock);
|
|
184
|
+
try {
|
|
185
|
+
const response = this.#transport.send(Object.freeze(canonicalize(projection)));
|
|
186
|
+
object(response, 'INVALID_TRANSPORT_RECEIPT', 'transport response');
|
|
187
|
+
if (response.receipt_ref) {
|
|
188
|
+
string(response.receipt_ref, 'INVALID_TRANSPORT_RECEIPT', 'receipt_ref');
|
|
189
|
+
if (this.#verifyReference(response.receipt_ref, 'receipt') !== true) fail('UNVERIFIED_RECEIPT', 'delivery receipt must be an immutable verified reference');
|
|
190
|
+
projection.delivery_state = 'confirmed';
|
|
191
|
+
projection.receipt_ref = response.receipt_ref;
|
|
192
|
+
projection.reconciliation_state = 'pending';
|
|
193
|
+
} else projection.delivery_state = 'sent_unconfirmed';
|
|
194
|
+
} catch (error) {
|
|
195
|
+
projection.delivery_state = error?.permanent === true ? 'permanent_failure' : 'retryable_failure';
|
|
196
|
+
projection.failure_code = typeof error?.code === 'string' ? error.code : 'TRANSPORT_FAILURE';
|
|
197
|
+
}
|
|
198
|
+
this.#setEvolution(projection);
|
|
199
|
+
this.#persist();
|
|
200
|
+
return Object.freeze(canonicalize(projection));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
reconcile({ projection_id } = {}) {
|
|
204
|
+
const projection = this.#find(projection_id);
|
|
205
|
+
if (!this.#transport || typeof this.#transport.reconcile !== 'function') fail('RECONCILIATION_NOT_CONFIGURED', 'no authorized reconciliation transport is configured');
|
|
206
|
+
if (projection.delivery_state !== 'confirmed') fail('RECEIPT_REQUIRED', 'reconciliation requires confirmed delivery receipt');
|
|
207
|
+
this.#preflightTransport(projection);
|
|
208
|
+
const result = this.#transport.reconcile(Object.freeze(canonicalize(projection)));
|
|
209
|
+
object(result, 'INVALID_RECONCILIATION_RESULT', 'reconciliation result');
|
|
210
|
+
if (!['reconciled', 'drift_detected', 'blocked'].includes(result.state)) fail('INVALID_RECONCILIATION_RESULT', 'reconciliation state is invalid');
|
|
211
|
+
projection.reconciliation_state = result.state;
|
|
212
|
+
if (result.evidence_ref !== undefined) { string(result.evidence_ref, 'INVALID_RECONCILIATION_RESULT', 'evidence_ref'); projection.reconciliation_evidence_ref = result.evidence_ref; }
|
|
213
|
+
this.#setEvolution(projection);
|
|
214
|
+
this.#persist();
|
|
215
|
+
return Object.freeze(canonicalize(projection));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
resolveSentUnconfirmed({ projection_id } = {}) {
|
|
219
|
+
const projection = this.#find(projection_id);
|
|
220
|
+
if (projection.delivery_state !== 'sent_unconfirmed') fail('NOT_SENT_UNCONFIRMED', 'idempotency lookup only applies to sent_unconfirmed delivery');
|
|
221
|
+
if (!this.#transport || typeof this.#transport.lookup !== 'function') fail('IDEMPOTENCY_LOOKUP_NOT_CONFIGURED', 'sent_unconfirmed requires an idempotency lookup transport');
|
|
222
|
+
this.#preflightTransport(projection);
|
|
223
|
+
const result = this.#transport.lookup(Object.freeze(canonicalize(projection)));
|
|
224
|
+
object(result, 'INVALID_LOOKUP_RESULT', 'idempotency lookup result');
|
|
225
|
+
if (result.receipt_ref !== undefined) {
|
|
226
|
+
string(result.receipt_ref, 'INVALID_LOOKUP_RESULT', 'receipt_ref');
|
|
227
|
+
if (this.#verifyReference(result.receipt_ref, 'receipt') !== true) fail('UNVERIFIED_RECEIPT', 'lookup receipt must be an immutable verified reference');
|
|
228
|
+
projection.delivery_state = 'confirmed';
|
|
229
|
+
projection.receipt_ref = result.receipt_ref;
|
|
230
|
+
projection.reconciliation_state = 'pending';
|
|
231
|
+
} else if (result.state === 'not_found') projection.delivery_state = 'pending';
|
|
232
|
+
else if (result.state !== 'unknown') fail('INVALID_LOOKUP_RESULT', 'lookup requires receipt_ref, not_found, or unknown');
|
|
233
|
+
this.#setEvolution(projection);
|
|
234
|
+
this.#persist();
|
|
235
|
+
return Object.freeze(canonicalize(projection));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
watchOnce() {
|
|
239
|
+
// Intentionally one-shot: a host scheduler may invoke this hourly. No timer is created.
|
|
240
|
+
const replayed = [];
|
|
241
|
+
for (const projection of this.#state.projections) {
|
|
242
|
+
if (projection.delivery_state === 'sent_unconfirmed') {
|
|
243
|
+
const resolved = this.resolveSentUnconfirmed({ projection_id: projection.projection_id });
|
|
244
|
+
if (resolved.delivery_state === 'pending') replayed.push(this.deliver({ projection_id: projection.projection_id }));
|
|
245
|
+
} else if (['pending', 'retryable_failure'].includes(projection.delivery_state)) replayed.push(this.deliver({ projection_id: projection.projection_id }));
|
|
246
|
+
}
|
|
247
|
+
return Object.freeze(replayed);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
rederiveProjectEvolution({ project_id } = {}) {
|
|
251
|
+
string(project_id, 'INVALID_PROJECT_ID', 'project_id');
|
|
252
|
+
if (!this.#state.projections.some((item) => item.project_id === project_id)) fail('PROJECT_NOT_FOUND', 'project has no durable projections', { project_id });
|
|
253
|
+
return this.#evolve(project_id);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
recordMetrics({ projection_id, actual_human_effort, actual_ai_processing, rework_cycles, rework_review_refs, completion } = {}) {
|
|
257
|
+
const projection = this.#find(projection_id);
|
|
258
|
+
object(actual_human_effort, 'INVALID_HUMAN_EFFORT', 'actual_human_effort');
|
|
259
|
+
object(actual_ai_processing, 'INVALID_AI_PROCESSING', 'actual_ai_processing');
|
|
260
|
+
if (!Number.isInteger(rework_cycles) || rework_cycles < 0) fail('INVALID_REWORK_CYCLES', 'rework_cycles must be a non-negative integer');
|
|
261
|
+
object(completion, 'MISSING_COMPLETION_EVIDENCE', 'completion');
|
|
262
|
+
if (this.#verifyCompletion(completion) !== true) fail('INVALID_COMPLETION_EVIDENCE', 'metrics require a valid C-06 completion');
|
|
263
|
+
if (!Array.isArray(rework_review_refs) || rework_review_refs.length !== rework_cycles || !rework_review_refs.every((ref) => typeof ref === 'string' && ref && this.#verifyReference(ref, 'review') === true)) {
|
|
264
|
+
fail('INVALID_REWORK_PROVENANCE', 'each rework cycle requires one verified C-05 review reference');
|
|
265
|
+
}
|
|
266
|
+
string(actual_human_effort.evidence_ref, 'INVALID_HUMAN_EFFORT', 'actual_human_effort.evidence_ref');
|
|
267
|
+
string(actual_ai_processing.evidence_ref, 'INVALID_AI_PROCESSING', 'actual_ai_processing.evidence_ref');
|
|
268
|
+
if (this.#verifyReference(actual_human_effort.evidence_ref, 'human-effort') !== true || this.#verifyReference(actual_ai_processing.evidence_ref, 'attempt-instrumentation') !== true) {
|
|
269
|
+
fail('UNVERIFIED_METRIC_PROVENANCE', 'metrics require verified explicit human and attempt instrumentation evidence');
|
|
270
|
+
}
|
|
271
|
+
// No wall-clock value exists here. Provenance is durable with the metric.
|
|
272
|
+
const metricMaterial = canonicalize({ actual_human_effort, actual_ai_processing, rework_cycles, rework_review_refs, completion_id: completion.completion_id });
|
|
273
|
+
if (projection.metrics) {
|
|
274
|
+
const { recorded_at: _recordedAt, ...existingMaterial } = projection.metrics;
|
|
275
|
+
if (sha256(existingMaterial) !== sha256(metricMaterial)) fail('METRICS_ALREADY_RECORDED', 'projection metrics are immutable');
|
|
276
|
+
return Object.freeze(canonicalize(projection));
|
|
277
|
+
}
|
|
278
|
+
projection.metrics = canonicalize({ ...metricMaterial, recorded_at: timestamp(this.#clock) });
|
|
279
|
+
this.#setEvolution(projection);
|
|
280
|
+
this.#persist();
|
|
281
|
+
return Object.freeze(canonicalize(projection));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function validateProjection(projection) {
|
|
286
|
+
object(projection, 'INVALID_PROJECTION', 'projection');
|
|
287
|
+
for (const field of ['projection_id', 'idempotency_key', 'canonical_event_id', 'task_id', 'clickup_ref', 'project_id', 'payload_hash']) string(projection[field], 'INVALID_PROJECTION', field);
|
|
288
|
+
if (!OPERATIONS.has(projection.operation)) fail('INVALID_PROJECTION_OPERATION', 'operation is invalid');
|
|
289
|
+
if (!DELIVERY_STATES.has(projection.delivery_state)) fail('INVALID_DELIVERY_STATE', 'delivery_state is invalid');
|
|
290
|
+
if (!RECONCILIATION_STATES.has(projection.reconciliation_state)) fail('INVALID_RECONCILIATION_STATE', 'reconciliation_state is invalid');
|
|
291
|
+
if (projection.delivery_state === 'confirmed' && typeof projection.receipt_ref !== 'string') fail('MISSING_RECEIPT', 'confirmed projection requires receipt_ref');
|
|
292
|
+
return Object.freeze(canonicalize(projection));
|
|
293
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chati-dev",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "AI-Powered Multi-Agent Orchestration System - Structured vibe coding for Full Stack Development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"sync": "node scripts/sync-framework.js",
|
|
29
29
|
"scan": "node scripts/scan-stale.js",
|
|
30
30
|
"install-to": "node scripts/install-to.js",
|
|
31
|
-
"prepublishOnly": "node scripts/sync-framework.js && node scripts/scan-stale.js && node scripts/sign-manifest.js && REQUIRE_SIGNED_MANIFEST=1 node scripts/validate-package.js",
|
|
31
|
+
"prepublishOnly": "node scripts/sync-framework.js && node scripts/scan-stale.js && node scripts/sign-model-catalog.js && node scripts/sign-manifest.js && REQUIRE_SIGNED_MANIFEST=1 node scripts/validate-package.js",
|
|
32
|
+
"prepack": "node scripts/prepare-bundled-dependencies.js",
|
|
33
|
+
"postpack": "node scripts/prepare-bundled-dependencies.js --clean",
|
|
32
34
|
"test": "node --test test/**/*.test.js",
|
|
33
35
|
"lint": "eslint src/ bin/",
|
|
34
36
|
"lint:fix": "eslint src/ bin/ --fix",
|
|
@@ -62,12 +64,32 @@
|
|
|
62
64
|
"url": "https://github.com/Chati-dev/Chati.dev/issues"
|
|
63
65
|
},
|
|
64
66
|
"dependencies": {
|
|
67
|
+
"@chati/browser-capability": "0.1.0",
|
|
68
|
+
"@chati/core": "0.1.0",
|
|
69
|
+
"@chati/knowledge-context": "0.1.0",
|
|
70
|
+
"@chati/planning": "0.1.0",
|
|
71
|
+
"@chati/provider-registry": "0.1.0",
|
|
72
|
+
"@chati/rail": "0.1.0",
|
|
73
|
+
"@chati/release-lane": "0.1.0",
|
|
74
|
+
"@chati/review-council": "0.1.0",
|
|
75
|
+
"@chati/tracking-clickup": "0.1.0",
|
|
65
76
|
"@clack/prompts": "^0.7.0",
|
|
66
77
|
"chalk": "^5.3.0",
|
|
67
|
-
"js-yaml": "^4.
|
|
78
|
+
"js-yaml": "^5.4.0",
|
|
68
79
|
"ora": "^8.0.1",
|
|
69
80
|
"semver": "^7.6.0"
|
|
70
81
|
},
|
|
82
|
+
"bundleDependencies": [
|
|
83
|
+
"@chati/browser-capability",
|
|
84
|
+
"@chati/core",
|
|
85
|
+
"@chati/knowledge-context",
|
|
86
|
+
"@chati/planning",
|
|
87
|
+
"@chati/provider-registry",
|
|
88
|
+
"@chati/rail",
|
|
89
|
+
"@chati/release-lane",
|
|
90
|
+
"@chati/review-council",
|
|
91
|
+
"@chati/tracking-clickup"
|
|
92
|
+
],
|
|
71
93
|
"engines": {
|
|
72
94
|
"node": ">=20.0.0"
|
|
73
95
|
},
|
|
@@ -37,6 +37,16 @@ export const IDE_CONFIGS = {
|
|
|
37
37
|
rulesPath: '.codex/rules/',
|
|
38
38
|
overrideFile: 'AGENTS.override.md',
|
|
39
39
|
},
|
|
40
|
+
'grok-cli': {
|
|
41
|
+
name: 'Grok CLI',
|
|
42
|
+
description: 'xAI terminal agent',
|
|
43
|
+
group: 'cli',
|
|
44
|
+
recommended: false,
|
|
45
|
+
configPath: '.grok/commands/',
|
|
46
|
+
rulesFile: 'AGENTS.md',
|
|
47
|
+
mcpConfigFile: '.grok/mcp.json',
|
|
48
|
+
formatNotes: 'Native markdown command format',
|
|
49
|
+
},
|
|
40
50
|
'vscode': {
|
|
41
51
|
name: 'VS Code',
|
|
42
52
|
description: 'Extensions: Continue, AI Chat, etc',
|
|
@@ -87,6 +97,7 @@ export const IDE_TO_PROVIDER = {
|
|
|
87
97
|
'claude-code': 'claude',
|
|
88
98
|
'gemini-cli': 'gemini',
|
|
89
99
|
'codex-cli': 'codex',
|
|
100
|
+
'grok-cli': 'grok',
|
|
90
101
|
'vscode': null,
|
|
91
102
|
'cursor': null,
|
|
92
103
|
'windsurf': null,
|
package/src/executors/runner.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { spawn } from 'child_process';
|
|
18
18
|
import { existsSync, readFileSync } from 'fs';
|
|
19
19
|
import { join, resolve, sep } from 'path';
|
|
20
|
-
import yaml from 'js-yaml';
|
|
20
|
+
import * as yaml from 'js-yaml';
|
|
21
21
|
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
22
22
|
|
|
23
23
|
/**
|
package/src/installer/core.js
CHANGED
|
@@ -23,7 +23,7 @@ const FRAMEWORK_SOURCE = existsSync(BUNDLED_SOURCE) ? BUNDLED_SOURCE : MONOREPO_
|
|
|
23
23
|
* Install Chati.dev framework into target directory
|
|
24
24
|
*/
|
|
25
25
|
export async function installFramework(config) {
|
|
26
|
-
const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName,
|
|
26
|
+
const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, allProviders } = config;
|
|
27
27
|
// Always derive version from package.json (single source of truth)
|
|
28
28
|
let { version } = config;
|
|
29
29
|
if (!version) {
|
|
@@ -55,7 +55,7 @@ export async function installFramework(config) {
|
|
|
55
55
|
if (!existsSync(sessionPath)) {
|
|
56
56
|
writeFileSync(
|
|
57
57
|
sessionPath,
|
|
58
|
-
generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs,
|
|
58
|
+
generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, allProviders }),
|
|
59
59
|
'utf-8'
|
|
60
60
|
);
|
|
61
61
|
}
|
|
@@ -114,12 +114,13 @@ export async function installFramework(config) {
|
|
|
114
114
|
createDir(join(memoriesBase, dir));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// Copy framework
|
|
118
|
-
|
|
117
|
+
// Copy the canonical framework as a provider-neutral fallback. Every enabled
|
|
118
|
+
// CLI receives its own explicit adapted overlay below.
|
|
119
|
+
copyFrameworkFiles(frameworkDir, 'claude');
|
|
119
120
|
|
|
120
|
-
// Generate provider overlays
|
|
121
|
-
if (allProviders && allProviders.length >
|
|
122
|
-
generateProviderOverlays(targetDir, FRAMEWORK_SOURCE,
|
|
121
|
+
// Generate symmetric provider overlays. No CLI is permanently primary.
|
|
122
|
+
if (allProviders && allProviders.length > 0) {
|
|
123
|
+
generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, allProviders);
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
// Bundle the CLI source into the framework dir so chati-router.js can run
|
|
@@ -159,19 +160,19 @@ export async function installFramework(config) {
|
|
|
159
160
|
// Write config.yaml
|
|
160
161
|
writeFileSync(
|
|
161
162
|
join(frameworkDir, 'config.yaml'),
|
|
162
|
-
generateConfigYaml({ version, projectType, language, selectedIDEs,
|
|
163
|
+
generateConfigYaml({ version, projectType, language, selectedIDEs, allProviders }),
|
|
163
164
|
'utf-8'
|
|
164
165
|
);
|
|
165
166
|
|
|
166
|
-
// 3. Configure
|
|
167
|
-
|
|
167
|
+
// 3. Configure every CLI against its own overlay. The CLI that opens a
|
|
168
|
+
// session becomes that session's orchestrator.
|
|
168
169
|
for (const ideKey of selectedIDEs) {
|
|
169
170
|
const ideProvider = IDE_TO_PROVIDER[ideKey];
|
|
170
|
-
const
|
|
171
|
-
const orchestratorPath =
|
|
171
|
+
const hasProviderOverlay = ideProvider && allProviders?.includes(ideProvider);
|
|
172
|
+
const orchestratorPath = hasProviderOverlay
|
|
172
173
|
? `.chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
|
|
173
174
|
: '.chati.dev/orchestrator/chati.md';
|
|
174
|
-
await configureIDE(targetDir, ideKey, selectedMCPs, { orchestratorPath, isSecondary, providerName: ideProvider });
|
|
175
|
+
await configureIDE(targetDir, ideKey, selectedMCPs, { orchestratorPath, isSecondary: false, providerName: ideProvider });
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
const hasClaude = selectedIDEs.includes('claude-code');
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @fileoverview Provider Overlay Generator for multi-CLI continuity.
|
|
3
3
|
*
|
|
4
4
|
* When multiple CLI providers are selected (e.g. Claude Code + Gemini CLI),
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
5
|
+
* the provider-neutral framework lives in chati.dev/ (main). For each
|
|
6
|
+
* enabled provider, this module generates an overlay directory at
|
|
7
7
|
* chati.dev/.adapted/<provider>/ with the ADAPTABLE_FILES adapted for
|
|
8
8
|
* that provider. Each CLI reads only its own files — zero runtime translation.
|
|
9
9
|
*
|
|
@@ -15,31 +15,24 @@ import { join, dirname } from 'path';
|
|
|
15
15
|
import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Generate adapted framework overlays for
|
|
18
|
+
* Generate adapted framework overlays for all enabled providers.
|
|
19
19
|
*
|
|
20
|
-
* For each
|
|
20
|
+
* For each CLI provider, creates chati.dev/.adapted/<provider>/
|
|
21
21
|
* with provider-adapted copies of all ADAPTABLE_FILES.
|
|
22
22
|
*
|
|
23
|
-
* Reads
|
|
24
|
-
* each
|
|
25
|
-
* primary provider is not Claude.
|
|
23
|
+
* Reads canonical source content from frameworkSource, then adapts it for
|
|
24
|
+
* each enabled provider. Every CLI resolves its own overlay symmetrically.
|
|
26
25
|
*
|
|
27
26
|
* @param {string} targetDir - Project root directory
|
|
28
27
|
* @param {string} frameworkSource - Source directory with canonical framework files
|
|
29
|
-
* @param {string} primaryProvider - Primary provider name ('claude', 'gemini', 'codex')
|
|
30
28
|
* @param {string[]} allProviders - All CLI provider names
|
|
31
29
|
* @returns {{ generated: string[], skipped: string[] }}
|
|
32
30
|
*/
|
|
33
|
-
export function generateProviderOverlays(targetDir, frameworkSource,
|
|
31
|
+
export function generateProviderOverlays(targetDir, frameworkSource, allProviders) {
|
|
34
32
|
const result = { generated: [], skipped: [] };
|
|
35
33
|
const frameworkDir = join(targetDir, '.chati.dev');
|
|
36
34
|
|
|
37
35
|
for (const provider of allProviders) {
|
|
38
|
-
if (provider === primaryProvider) {
|
|
39
|
-
result.skipped.push(provider);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
36
|
const overlayDir = join(frameworkDir, '.adapted', provider);
|
|
44
37
|
|
|
45
38
|
for (const file of ADAPTABLE_FILES) {
|
|
@@ -64,7 +57,7 @@ export function generateProviderOverlays(targetDir, frameworkSource, primaryProv
|
|
|
64
57
|
/**
|
|
65
58
|
* Resolve the correct framework file path for a given provider.
|
|
66
59
|
*
|
|
67
|
-
* Returns the overlay path
|
|
60
|
+
* Returns the provider overlay path when it exists,
|
|
68
61
|
* otherwise falls back to the main chati.dev/ path.
|
|
69
62
|
*
|
|
70
63
|
* @param {string} projectDir - Project root directory
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from 'fs';
|
|
25
25
|
import { join, dirname } from 'path';
|
|
26
26
|
import { fileURLToPath } from 'url';
|
|
27
|
-
import yaml from 'js-yaml';
|
|
27
|
+
import * as yaml from 'js-yaml';
|
|
28
28
|
import { hashContent } from './file-hasher.js';
|
|
29
29
|
|
|
30
30
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import yaml from 'js-yaml';
|
|
1
|
+
import * as yaml from 'js-yaml';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Generate session.yaml content
|
|
5
5
|
*/
|
|
6
6
|
export function generateSessionYaml(config) {
|
|
7
|
-
const { projectName, projectType, language, selectedIDEs, selectedMCPs,
|
|
7
|
+
const { projectName, projectType, language, selectedIDEs, selectedMCPs, allProviders } = config;
|
|
8
8
|
|
|
9
9
|
// Schema MUST match session-manager.js DEFAULT_SESSION fields,
|
|
10
10
|
// otherwise validateSession() rejects installer-generated sessions.
|
|
@@ -27,9 +27,7 @@ export function generateSessionYaml(config) {
|
|
|
27
27
|
user_level_confidence: 0.0,
|
|
28
28
|
ides: selectedIDEs,
|
|
29
29
|
mcps: selectedMCPs,
|
|
30
|
-
providers_enabled: allProviders && allProviders.length > 0 ? allProviders : [
|
|
31
|
-
primary_provider: llmProvider || 'claude',
|
|
32
|
-
active_provider: llmProvider || 'claude',
|
|
30
|
+
providers_enabled: allProviders && allProviders.length > 0 ? allProviders : ['claude'],
|
|
33
31
|
completed_agents: [],
|
|
34
32
|
agent_results: {},
|
|
35
33
|
mode_transitions: [],
|
|
@@ -60,7 +58,7 @@ export function generateSessionYaml(config) {
|
|
|
60
58
|
};
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
return yaml.dump(session, { lineWidth: -1,
|
|
61
|
+
return yaml.dump(session, { lineWidth: -1, quoteStyle: 'double', forceQuotes: false });
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/**
|
|
@@ -102,10 +100,9 @@ export const PROVIDER_MODEL_MAPS = {
|
|
|
102
100
|
* Generate config.yaml content
|
|
103
101
|
*/
|
|
104
102
|
export function generateConfigYaml(config) {
|
|
105
|
-
const { version, projectType, language,
|
|
103
|
+
const { version, projectType, language, allProviders } = config;
|
|
106
104
|
const selectedIDEs = config.selectedIDEs || [];
|
|
107
|
-
const
|
|
108
|
-
const providers = allProviders && allProviders.length > 0 ? allProviders : [provider];
|
|
105
|
+
const providers = allProviders && allProviders.length > 0 ? allProviders : ['claude'];
|
|
109
106
|
|
|
110
107
|
const configData = {
|
|
111
108
|
version: version,
|
|
@@ -116,9 +113,10 @@ export function generateConfigYaml(config) {
|
|
|
116
113
|
language: language,
|
|
117
114
|
ides: selectedIDEs,
|
|
118
115
|
providers: {
|
|
119
|
-
claude: { enabled: providers.includes('claude')
|
|
120
|
-
gemini: { enabled: providers.includes('gemini') || selectedIDEs.includes('gemini-cli')
|
|
121
|
-
codex: { enabled: providers.includes('codex') || selectedIDEs.includes('codex-cli')
|
|
116
|
+
claude: { enabled: providers.includes('claude') },
|
|
117
|
+
gemini: { enabled: providers.includes('gemini') || selectedIDEs.includes('gemini-cli') },
|
|
118
|
+
codex: { enabled: providers.includes('codex') || selectedIDEs.includes('codex-cli') },
|
|
119
|
+
grok: { enabled: providers.includes('grok') || selectedIDEs.includes('grok-cli') },
|
|
122
120
|
},
|
|
123
121
|
};
|
|
124
122
|
|
|
@@ -148,7 +146,7 @@ export function generateConfigYaml(config) {
|
|
|
148
146
|
frustration_detection: true,
|
|
149
147
|
bash_security_checks: true,
|
|
150
148
|
// Agent Teams (Article XXI) — default ON in v4.2.2.
|
|
151
|
-
// Only effective when
|
|
149
|
+
// Only effective when the active session runs in Claude (gated in cli.js
|
|
152
150
|
// isAgentTeamsEnabled). Gemini and Codex always fall back to sequential
|
|
153
151
|
// pipeline silently. See plan: was previously false for "safe rollout"
|
|
154
152
|
// but the v4.2.0 launch never actually shipped, so this is a fresh start.
|
|
@@ -165,18 +163,7 @@ export function generateConfigYaml(config) {
|
|
|
165
163
|
anonymous_id: null,
|
|
166
164
|
};
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
if (provider !== 'claude') {
|
|
170
|
-
const modelMap = PROVIDER_MODEL_MAPS[provider];
|
|
171
|
-
if (modelMap) {
|
|
172
|
-
configData.agent_overrides = {};
|
|
173
|
-
for (const [agent, model] of Object.entries(modelMap.agents)) {
|
|
174
|
-
configData.agent_overrides[agent] = { provider, model };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return yaml.dump(configData, { lineWidth: -1, quotingType: '"', forceQuotes: false });
|
|
166
|
+
return yaml.dump(configData, { lineWidth: -1, quoteStyle: 'double', forceQuotes: false });
|
|
180
167
|
}
|
|
181
168
|
|
|
182
169
|
/**
|