@kognai/orchestrator-core 0.1.0
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 +44 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +175 -0
- package/dist/lib/aar-middleware.d.ts +6 -0
- package/dist/lib/aar-middleware.js +70 -0
- package/dist/lib/aar-types.d.ts +34 -0
- package/dist/lib/aar-types.js +4 -0
- package/dist/lib/acp-engine.d.ts +68 -0
- package/dist/lib/acp-engine.js +123 -0
- package/dist/lib/acp.d.ts +61 -0
- package/dist/lib/acp.js +425 -0
- package/dist/lib/agent-registry.d.ts +50 -0
- package/dist/lib/agent-registry.js +137 -0
- package/dist/lib/anthropic-direct.d.ts +27 -0
- package/dist/lib/anthropic-direct.js +109 -0
- package/dist/lib/asmr-extractor.d.ts +40 -0
- package/dist/lib/asmr-extractor.js +151 -0
- package/dist/lib/asmr-retrieval.d.ts +76 -0
- package/dist/lib/asmr-retrieval.js +311 -0
- package/dist/lib/asmr.d.ts +8 -0
- package/dist/lib/asmr.js +24 -0
- package/dist/lib/brainx-client.d.ts +72 -0
- package/dist/lib/brainx-client.js +200 -0
- package/dist/lib/brainx-embed.d.ts +14 -0
- package/dist/lib/brainx-embed.js +139 -0
- package/dist/lib/brainx-swarm-bridge.d.ts +93 -0
- package/dist/lib/brainx-swarm-bridge.js +242 -0
- package/dist/lib/byterover-client.d.ts +19 -0
- package/dist/lib/byterover-client.js +59 -0
- package/dist/lib/ceo-wallet.d.ts +37 -0
- package/dist/lib/ceo-wallet.js +176 -0
- package/dist/lib/chomsky-gate.d.ts +24 -0
- package/dist/lib/chomsky-gate.js +178 -0
- package/dist/lib/chomsky-runner.d.ts +29 -0
- package/dist/lib/chomsky-runner.js +157 -0
- package/dist/lib/citizen-score-contract.d.ts +72 -0
- package/dist/lib/citizen-score-contract.js +16 -0
- package/dist/lib/citizen-score-registry.d.ts +25 -0
- package/dist/lib/citizen-score-registry.js +65 -0
- package/dist/lib/citizenship.d.ts +103 -0
- package/dist/lib/citizenship.js +272 -0
- package/dist/lib/clawrouter-client.d.ts +37 -0
- package/dist/lib/clawrouter-client.js +148 -0
- package/dist/lib/code-asset-crystalliser.d.ts +41 -0
- package/dist/lib/code-asset-crystalliser.js +181 -0
- package/dist/lib/code-failure-logger.d.ts +27 -0
- package/dist/lib/code-failure-logger.js +42 -0
- package/dist/lib/cto-approval-gate.d.ts +45 -0
- package/dist/lib/cto-approval-gate.js +478 -0
- package/dist/lib/cto-gate-types.d.ts +28 -0
- package/dist/lib/cto-gate-types.js +8 -0
- package/dist/lib/decomposer-feedback.d.ts +54 -0
- package/dist/lib/decomposer-feedback.js +115 -0
- package/dist/lib/emotional-safety-gate.d.ts +48 -0
- package/dist/lib/emotional-safety-gate.js +97 -0
- package/dist/lib/engine-paths.d.ts +13 -0
- package/dist/lib/engine-paths.js +32 -0
- package/dist/lib/event-bus-listener.d.ts +8 -0
- package/dist/lib/event-bus-listener.js +144 -0
- package/dist/lib/event-bus-publisher.d.ts +25 -0
- package/dist/lib/event-bus-publisher.js +188 -0
- package/dist/lib/event-bus-types.d.ts +73 -0
- package/dist/lib/event-bus-types.js +23 -0
- package/dist/lib/failure-library.d.ts +178 -0
- package/dist/lib/failure-library.js +349 -0
- package/dist/lib/ksl/error-log.d.ts +28 -0
- package/dist/lib/ksl/error-log.js +43 -0
- package/dist/lib/ksl/index.d.ts +9 -0
- package/dist/lib/ksl/index.js +25 -0
- package/dist/lib/ksl/orchestrator-tap.d.ts +16 -0
- package/dist/lib/ksl/orchestrator-tap.js +85 -0
- package/dist/lib/ksl/record-writer.d.ts +46 -0
- package/dist/lib/ksl/record-writer.js +45 -0
- package/dist/lib/llm-cost-table.d.ts +36 -0
- package/dist/lib/llm-cost-table.js +90 -0
- package/dist/lib/local-model-router.d.ts +27 -0
- package/dist/lib/local-model-router.js +61 -0
- package/dist/lib/mc-client.d.ts +51 -0
- package/dist/lib/mc-client.js +249 -0
- package/dist/lib/model-router-contract.d.ts +91 -0
- package/dist/lib/model-router-contract.js +19 -0
- package/dist/lib/model-router-registry.d.ts +24 -0
- package/dist/lib/model-router-registry.js +52 -0
- package/dist/lib/model-router.d.ts +20 -0
- package/dist/lib/model-router.js +79 -0
- package/dist/lib/monotask-state-machine.d.ts +19 -0
- package/dist/lib/monotask-state-machine.js +131 -0
- package/dist/lib/neutral-prompt-checker.d.ts +22 -0
- package/dist/lib/neutral-prompt-checker.js +130 -0
- package/dist/lib/notion-direct.d.ts +92 -0
- package/dist/lib/notion-direct.js +381 -0
- package/dist/lib/ollama-client.d.ts +37 -0
- package/dist/lib/ollama-client.js +158 -0
- package/dist/lib/omel/credential-vault.d.ts +57 -0
- package/dist/lib/omel/credential-vault.js +324 -0
- package/dist/lib/omel/human-brake.d.ts +32 -0
- package/dist/lib/omel/human-brake.js +289 -0
- package/dist/lib/omel/index.d.ts +10 -0
- package/dist/lib/omel/index.js +26 -0
- package/dist/lib/omel/phantom-workspace.d.ts +31 -0
- package/dist/lib/omel/phantom-workspace.js +256 -0
- package/dist/lib/omel/wipe-witness.d.ts +75 -0
- package/dist/lib/omel/wipe-witness.js +398 -0
- package/dist/lib/orchestrate-engine.d.ts +25 -0
- package/dist/lib/orchestrate-engine.js +4436 -0
- package/dist/lib/perm-judge.d.ts +46 -0
- package/dist/lib/perm-judge.js +173 -0
- package/dist/lib/plumber/conformance.d.ts +54 -0
- package/dist/lib/plumber/conformance.js +121 -0
- package/dist/lib/plumber/index.d.ts +9 -0
- package/dist/lib/plumber/index.js +25 -0
- package/dist/lib/plumber/observer.d.ts +52 -0
- package/dist/lib/plumber/observer.js +180 -0
- package/dist/lib/plumber/types.d.ts +78 -0
- package/dist/lib/plumber/types.js +29 -0
- package/dist/lib/research-impl-gate.d.ts +16 -0
- package/dist/lib/research-impl-gate.js +105 -0
- package/dist/lib/sherlock-memory.d.ts +29 -0
- package/dist/lib/sherlock-memory.js +105 -0
- package/dist/lib/skill-crystalliser.d.ts +44 -0
- package/dist/lib/skill-crystalliser.js +60 -0
- package/dist/lib/sprint-runner-engine.d.ts +27 -0
- package/dist/lib/sprint-runner-engine.js +1042 -0
- package/dist/lib/sprint-state.d.ts +71 -0
- package/dist/lib/sprint-state.js +202 -0
- package/dist/lib/stuck-handler.d.ts +17 -0
- package/dist/lib/stuck-handler.js +249 -0
- package/dist/lib/task-contract-checker.d.ts +17 -0
- package/dist/lib/task-contract-checker.js +29 -0
- package/dist/lib/task-router/index.d.ts +17 -0
- package/dist/lib/task-router/index.js +52 -0
- package/dist/lib/task-router/router/generate-execution-id.d.ts +10 -0
- package/dist/lib/task-router/router/generate-execution-id.js +24 -0
- package/dist/lib/task-router/router/resolve-route.d.ts +2 -0
- package/dist/lib/task-router/router/resolve-route.js +49 -0
- package/dist/lib/task-router/types.d.ts +79 -0
- package/dist/lib/task-router/types.js +39 -0
- package/dist/lib/token-budget-validator.d.ts +44 -0
- package/dist/lib/token-budget-validator.js +84 -0
- package/dist/lib/trust-score-updater.d.ts +30 -0
- package/dist/lib/trust-score-updater.js +107 -0
- package/dist/lib/wallet-state.d.ts +26 -0
- package/dist/lib/wallet-state.js +85 -0
- package/package.json +27 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sprint-state.ts — TICKET-098: split sprint task definitions (committed,
|
|
3
|
+
* immutable) from sprint task runtime status (gitignored, mutable).
|
|
4
|
+
*
|
|
5
|
+
* Problem solved: prior design stored `status`, `attempt_count`,
|
|
6
|
+
* `rejected_reason` inside `workspace/sprints/sprint-XXXX.json` which is
|
|
7
|
+
* git-tracked. Any git operation (rebase, fetch, checkout) that touched
|
|
8
|
+
* the file silently clobbered runtime state. Multiple writers racing on
|
|
9
|
+
* the same file made it worse. The push-with-rebase fix (TICKET-095) made
|
|
10
|
+
* it catastrophic by ACTIVELY resetting source to origin on every
|
|
11
|
+
* divergent push.
|
|
12
|
+
*
|
|
13
|
+
* Solution: source file contains ONLY task definitions. Runtime status
|
|
14
|
+
* lives in `.swarm-state/sprint-XXXX.json` (gitignored). Git never sees
|
|
15
|
+
* it. Multiple writers still need atomic-write but no longer race with
|
|
16
|
+
* git itself.
|
|
17
|
+
*
|
|
18
|
+
* TICKET-215 Phase 3b-3: the repo root now resolves via engine-paths
|
|
19
|
+
* (KOGNAI_ROOT / cwd) instead of `process.cwd()` directly, so the module is
|
|
20
|
+
* location-independent and can live in @kognai/orchestrator-core. When run
|
|
21
|
+
* from a product repo root, resolveEnginePaths().root === the old cwd — same
|
|
22
|
+
* <root>/.swarm-state path, no behavior change.
|
|
23
|
+
*/
|
|
24
|
+
export interface TaskState {
|
|
25
|
+
status: string;
|
|
26
|
+
attempt_count?: number;
|
|
27
|
+
rejected_reason?: string;
|
|
28
|
+
}
|
|
29
|
+
export type SprintStateMap = Record<string, TaskState>;
|
|
30
|
+
/** Read the gitignored state file for a sprint. Returns empty map if absent. */
|
|
31
|
+
export declare function readState(sprintFile: string): SprintStateMap;
|
|
32
|
+
/**
|
|
33
|
+
* Update one task's runtime status. Idempotent — no-op if nothing changed.
|
|
34
|
+
* Caller should pass undefined for fields that shouldn't be touched.
|
|
35
|
+
*/
|
|
36
|
+
export declare function updateTaskStatus(sprintFile: string, taskId: string, status?: string, attempt_count?: number, rejected_reason?: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Bulk apply task statuses from an ACTIVE file's tasks array. Used by
|
|
39
|
+
* sprint-runner sync-on-exit to mirror orchestrator's per-task updates
|
|
40
|
+
* back into persistent state. Idempotent at the field level.
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyActiveTasksToState(sprintFile: string, activeTasks: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
status?: string;
|
|
45
|
+
attempt_count?: number;
|
|
46
|
+
rejected_reason?: string;
|
|
47
|
+
score?: number;
|
|
48
|
+
grade?: string;
|
|
49
|
+
rejection_reason?: string;
|
|
50
|
+
}>): {
|
|
51
|
+
updates: number;
|
|
52
|
+
attempted: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Merge source (definitions) + state (runtime) into the unified shape the
|
|
56
|
+
* orchestrator expects. Used at runtime to build the ACTIVE file.
|
|
57
|
+
*
|
|
58
|
+
* Backward-compat: if source still has status fields (pre-migration), use
|
|
59
|
+
* them as initial values; state always wins when present.
|
|
60
|
+
*/
|
|
61
|
+
export declare function loadSprintMerged(sprintFile: string): any;
|
|
62
|
+
/**
|
|
63
|
+
* One-shot migration helper: read every sprint file in workspace/sprints/,
|
|
64
|
+
* if it contains tasks with status/attempt_count/rejected_reason inline,
|
|
65
|
+
* extract those fields into .swarm-state/ and strip them from source.
|
|
66
|
+
* Idempotent — safe to run multiple times.
|
|
67
|
+
*/
|
|
68
|
+
export declare function migrateSprintFile(sprintFile: string): {
|
|
69
|
+
migrated: number;
|
|
70
|
+
sourceStripped: boolean;
|
|
71
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* sprint-state.ts — TICKET-098: split sprint task definitions (committed,
|
|
4
|
+
* immutable) from sprint task runtime status (gitignored, mutable).
|
|
5
|
+
*
|
|
6
|
+
* Problem solved: prior design stored `status`, `attempt_count`,
|
|
7
|
+
* `rejected_reason` inside `workspace/sprints/sprint-XXXX.json` which is
|
|
8
|
+
* git-tracked. Any git operation (rebase, fetch, checkout) that touched
|
|
9
|
+
* the file silently clobbered runtime state. Multiple writers racing on
|
|
10
|
+
* the same file made it worse. The push-with-rebase fix (TICKET-095) made
|
|
11
|
+
* it catastrophic by ACTIVELY resetting source to origin on every
|
|
12
|
+
* divergent push.
|
|
13
|
+
*
|
|
14
|
+
* Solution: source file contains ONLY task definitions. Runtime status
|
|
15
|
+
* lives in `.swarm-state/sprint-XXXX.json` (gitignored). Git never sees
|
|
16
|
+
* it. Multiple writers still need atomic-write but no longer race with
|
|
17
|
+
* git itself.
|
|
18
|
+
*
|
|
19
|
+
* TICKET-215 Phase 3b-3: the repo root now resolves via engine-paths
|
|
20
|
+
* (KOGNAI_ROOT / cwd) instead of `process.cwd()` directly, so the module is
|
|
21
|
+
* location-independent and can live in @kognai/orchestrator-core. When run
|
|
22
|
+
* from a product repo root, resolveEnginePaths().root === the old cwd — same
|
|
23
|
+
* <root>/.swarm-state path, no behavior change.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.readState = readState;
|
|
27
|
+
exports.updateTaskStatus = updateTaskStatus;
|
|
28
|
+
exports.applyActiveTasksToState = applyActiveTasksToState;
|
|
29
|
+
exports.loadSprintMerged = loadSprintMerged;
|
|
30
|
+
exports.migrateSprintFile = migrateSprintFile;
|
|
31
|
+
const fs_1 = require("fs");
|
|
32
|
+
const path_1 = require("path");
|
|
33
|
+
const engine_paths_1 = require("./engine-paths");
|
|
34
|
+
const ROOT = (0, engine_paths_1.resolveEnginePaths)().root;
|
|
35
|
+
const STATE_DIR = (0, path_1.join)(ROOT, '.swarm-state');
|
|
36
|
+
function ensureStateDir() {
|
|
37
|
+
if (!(0, fs_1.existsSync)(STATE_DIR)) {
|
|
38
|
+
(0, fs_1.mkdirSync)(STATE_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function stateFileFor(sprintFile) {
|
|
42
|
+
const name = (0, path_1.basename)(sprintFile);
|
|
43
|
+
return (0, path_1.join)(STATE_DIR, name);
|
|
44
|
+
}
|
|
45
|
+
/** Read the gitignored state file for a sprint. Returns empty map if absent. */
|
|
46
|
+
function readState(sprintFile) {
|
|
47
|
+
const sf = stateFileFor(sprintFile);
|
|
48
|
+
if (!(0, fs_1.existsSync)(sf))
|
|
49
|
+
return {};
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse((0, fs_1.readFileSync)(sf, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Atomic write of the state map for a sprint. */
|
|
58
|
+
function writeState(sprintFile, state) {
|
|
59
|
+
ensureStateDir();
|
|
60
|
+
const sf = stateFileFor(sprintFile);
|
|
61
|
+
const tmp = `${sf}.tmp.${process.pid}.${Date.now()}`;
|
|
62
|
+
(0, fs_1.writeFileSync)(tmp, JSON.stringify(state, null, 2));
|
|
63
|
+
(0, fs_1.renameSync)(tmp, sf);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Update one task's runtime status. Idempotent — no-op if nothing changed.
|
|
67
|
+
* Caller should pass undefined for fields that shouldn't be touched.
|
|
68
|
+
*/
|
|
69
|
+
function updateTaskStatus(sprintFile, taskId, status, attempt_count, rejected_reason) {
|
|
70
|
+
const state = readState(sprintFile);
|
|
71
|
+
const prev = state[taskId] || { status: 'pending' };
|
|
72
|
+
const next = { ...prev };
|
|
73
|
+
let changed = false;
|
|
74
|
+
if (status !== undefined && status !== prev.status) {
|
|
75
|
+
next.status = status;
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
if (attempt_count !== undefined && attempt_count !== prev.attempt_count) {
|
|
79
|
+
next.attempt_count = attempt_count;
|
|
80
|
+
changed = true;
|
|
81
|
+
}
|
|
82
|
+
if (rejected_reason !== undefined && rejected_reason !== prev.rejected_reason) {
|
|
83
|
+
next.rejected_reason = rejected_reason;
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
if (!changed)
|
|
87
|
+
return;
|
|
88
|
+
state[taskId] = next;
|
|
89
|
+
writeState(sprintFile, state);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Bulk apply task statuses from an ACTIVE file's tasks array. Used by
|
|
93
|
+
* sprint-runner sync-on-exit to mirror orchestrator's per-task updates
|
|
94
|
+
* back into persistent state. Idempotent at the field level.
|
|
95
|
+
*/
|
|
96
|
+
function applyActiveTasksToState(sprintFile, activeTasks) {
|
|
97
|
+
const state = readState(sprintFile);
|
|
98
|
+
let updates = 0;
|
|
99
|
+
let attempted = 0;
|
|
100
|
+
for (const t of activeTasks) {
|
|
101
|
+
if (!t.id || !t.status)
|
|
102
|
+
continue;
|
|
103
|
+
const prev = (state[t.id] || { status: 'pending' });
|
|
104
|
+
// CTO-006 telemetry-blackout hotfix (2026-05-29, Slice A of TICKET-135):
|
|
105
|
+
// previously only updated when status changed, which meant attempt_count,
|
|
106
|
+
// score etc. silently dropped across retries (status stayed 'rejected'
|
|
107
|
+
// through 3 attempts → 2nd + 3rd attempt's fields lost). The CTO's
|
|
108
|
+
// 10-sprint zero-ship diagnosis turned on score being '?' in every
|
|
109
|
+
// retrospective. Now: detect ANY field change and merge through. Status
|
|
110
|
+
// transition still drives the 'attempted' counter.
|
|
111
|
+
const merged = {
|
|
112
|
+
status: t.status,
|
|
113
|
+
attempt_count: t.attempt_count ?? prev.attempt_count,
|
|
114
|
+
rejected_reason: t.rejected_reason ?? prev.rejected_reason,
|
|
115
|
+
score: t.score ?? prev.score,
|
|
116
|
+
grade: t.grade ?? prev.grade,
|
|
117
|
+
rejection_reason: t.rejection_reason ?? prev.rejection_reason,
|
|
118
|
+
};
|
|
119
|
+
const changed = JSON.stringify(merged) !== JSON.stringify(prev);
|
|
120
|
+
if (changed) {
|
|
121
|
+
state[t.id] = merged;
|
|
122
|
+
updates++;
|
|
123
|
+
if (prev.status !== t.status && t.status !== 'pending')
|
|
124
|
+
attempted++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (updates > 0)
|
|
128
|
+
writeState(sprintFile, state);
|
|
129
|
+
return { updates, attempted };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Merge source (definitions) + state (runtime) into the unified shape the
|
|
133
|
+
* orchestrator expects. Used at runtime to build the ACTIVE file.
|
|
134
|
+
*
|
|
135
|
+
* Backward-compat: if source still has status fields (pre-migration), use
|
|
136
|
+
* them as initial values; state always wins when present.
|
|
137
|
+
*/
|
|
138
|
+
function loadSprintMerged(sprintFile) {
|
|
139
|
+
const source = JSON.parse((0, fs_1.readFileSync)(sprintFile, 'utf-8'));
|
|
140
|
+
const state = readState(sprintFile);
|
|
141
|
+
if (Array.isArray(source.tasks)) {
|
|
142
|
+
source.tasks = source.tasks.map((t) => {
|
|
143
|
+
const s = state[t.id];
|
|
144
|
+
if (!s) {
|
|
145
|
+
// No state entry yet — keep source's status (or default to pending)
|
|
146
|
+
return { ...t, status: t.status ?? 'pending', attempt_count: t.attempt_count ?? 0 };
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...t,
|
|
150
|
+
status: s.status,
|
|
151
|
+
attempt_count: s.attempt_count ?? 0,
|
|
152
|
+
...(s.rejected_reason ? { rejected_reason: s.rejected_reason } : {}),
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return source;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One-shot migration helper: read every sprint file in workspace/sprints/,
|
|
160
|
+
* if it contains tasks with status/attempt_count/rejected_reason inline,
|
|
161
|
+
* extract those fields into .swarm-state/ and strip them from source.
|
|
162
|
+
* Idempotent — safe to run multiple times.
|
|
163
|
+
*/
|
|
164
|
+
function migrateSprintFile(sprintFile) {
|
|
165
|
+
if (!(0, fs_1.existsSync)(sprintFile))
|
|
166
|
+
return { migrated: 0, sourceStripped: false };
|
|
167
|
+
const source = JSON.parse((0, fs_1.readFileSync)(sprintFile, 'utf-8'));
|
|
168
|
+
if (!Array.isArray(source.tasks))
|
|
169
|
+
return { migrated: 0, sourceStripped: false };
|
|
170
|
+
const state = readState(sprintFile);
|
|
171
|
+
let migrated = 0;
|
|
172
|
+
let stripped = false;
|
|
173
|
+
for (const t of source.tasks) {
|
|
174
|
+
if (!t.id)
|
|
175
|
+
continue;
|
|
176
|
+
const hasRuntime = (t.status !== undefined) || (t.attempt_count !== undefined) || (t.rejected_reason !== undefined);
|
|
177
|
+
if (hasRuntime) {
|
|
178
|
+
// Only adopt source's runtime fields if state doesn't already have them
|
|
179
|
+
if (!state[t.id]) {
|
|
180
|
+
state[t.id] = {
|
|
181
|
+
status: t.status ?? 'pending',
|
|
182
|
+
attempt_count: t.attempt_count ?? 0,
|
|
183
|
+
...(t.rejected_reason ? { rejected_reason: t.rejected_reason } : {}),
|
|
184
|
+
};
|
|
185
|
+
migrated++;
|
|
186
|
+
}
|
|
187
|
+
delete t.status;
|
|
188
|
+
delete t.attempt_count;
|
|
189
|
+
delete t.rejected_reason;
|
|
190
|
+
stripped = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (migrated > 0)
|
|
194
|
+
writeState(sprintFile, state);
|
|
195
|
+
if (stripped) {
|
|
196
|
+
// Atomic write of source
|
|
197
|
+
const tmp = `${sprintFile}.tmp.${process.pid}.${Date.now()}`;
|
|
198
|
+
(0, fs_1.writeFileSync)(tmp, JSON.stringify(source, null, 2));
|
|
199
|
+
(0, fs_1.renameSync)(tmp, sprintFile);
|
|
200
|
+
}
|
|
201
|
+
return { migrated, sourceStripped: stripped };
|
|
202
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface StuckDetection {
|
|
2
|
+
stuck: boolean;
|
|
3
|
+
reason: string;
|
|
4
|
+
scores: number[];
|
|
5
|
+
rejection_reasons: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface EscalationDecision {
|
|
8
|
+
action: 'bump_tier' | 'rephrase_context' | 'escalate_human';
|
|
9
|
+
reasoning: string;
|
|
10
|
+
new_tier?: 'cloud-code' | 'apex';
|
|
11
|
+
budget_override?: number;
|
|
12
|
+
rephrased_context?: string;
|
|
13
|
+
telegram_message?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function escalationPaused(swarmStateDir?: string): boolean;
|
|
16
|
+
export declare function detectStuckPattern(sprintId: string, taskId: string, swarmStateDir?: string): StuckDetection;
|
|
17
|
+
export declare function escalateStuckTask(sprintId: string, taskId: string, taskContext: string, taskTitle: string, detection: StuckDetection, swarmStateDir?: string): Promise<EscalationDecision>;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.escalationPaused = escalationPaused;
|
|
4
|
+
exports.detectStuckPattern = detectStuckPattern;
|
|
5
|
+
exports.escalateStuckTask = escalateStuckTask;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const anthropic_direct_1 = require("./anthropic-direct");
|
|
9
|
+
const DEFAULT_SWARM_STATE_DIR = '.swarm-state';
|
|
10
|
+
// Kill-switch filename — matches TICKET-204 spec + the TICKET-201 dispatcher-paused
|
|
11
|
+
// convention (.swarm-state/<feature>-paused). `touch .swarm-state/escalation-paused`
|
|
12
|
+
// to disable all stuck-task escalation without a code change.
|
|
13
|
+
const PAUSE_FLAG = 'escalation-paused';
|
|
14
|
+
function envInt(name, fallback) {
|
|
15
|
+
const raw = process.env[name];
|
|
16
|
+
if (!raw)
|
|
17
|
+
return fallback;
|
|
18
|
+
const parsed = Number.parseInt(raw, 10);
|
|
19
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
20
|
+
}
|
|
21
|
+
function envFloat(name, fallback) {
|
|
22
|
+
const raw = process.env[name];
|
|
23
|
+
if (!raw)
|
|
24
|
+
return fallback;
|
|
25
|
+
const parsed = Number.parseFloat(raw);
|
|
26
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
27
|
+
}
|
|
28
|
+
function variance(values) {
|
|
29
|
+
if (values.length === 0)
|
|
30
|
+
return 0;
|
|
31
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
32
|
+
const sq = values.reduce((acc, v) => acc + (v - mean) ** 2, 0);
|
|
33
|
+
return sq / values.length;
|
|
34
|
+
}
|
|
35
|
+
function readStateFile(swarmStateDir, sprintId) {
|
|
36
|
+
const path = (0, node_path_1.join)(swarmStateDir, `${sprintId}.json`);
|
|
37
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const raw = (0, node_fs_1.readFileSync)(path, 'utf8');
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (parsed && typeof parsed === 'object' && parsed.tasks && typeof parsed.tasks === 'object') {
|
|
43
|
+
return parsed.tasks;
|
|
44
|
+
}
|
|
45
|
+
if (parsed && typeof parsed === 'object') {
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function escalationPaused(swarmStateDir = DEFAULT_SWARM_STATE_DIR) {
|
|
55
|
+
return (0, node_fs_1.existsSync)((0, node_path_1.join)(swarmStateDir, PAUSE_FLAG));
|
|
56
|
+
}
|
|
57
|
+
function detectStuckPattern(sprintId, taskId, swarmStateDir = DEFAULT_SWARM_STATE_DIR) {
|
|
58
|
+
const minAttempts = envInt('STUCK_MIN_ATTEMPTS', 3);
|
|
59
|
+
const varianceThreshold = envFloat('STUCK_VARIANCE_THRESHOLD', 5);
|
|
60
|
+
const scoreCeiling = envFloat('STUCK_SCORE_CEILING', 40);
|
|
61
|
+
const empty = { stuck: false, reason: '', scores: [], rejection_reasons: [] };
|
|
62
|
+
const tasks = readStateFile(swarmStateDir, sprintId);
|
|
63
|
+
if (!tasks)
|
|
64
|
+
return { ...empty, reason: 'no_state_file' };
|
|
65
|
+
const entry = tasks[taskId];
|
|
66
|
+
if (!entry)
|
|
67
|
+
return { ...empty, reason: 'no_task_entry' };
|
|
68
|
+
if (entry.status === 'done' || entry.status === 'awaiting_human') {
|
|
69
|
+
return { ...empty, reason: `status_${entry.status}` };
|
|
70
|
+
}
|
|
71
|
+
const attemptCount = entry.attempt_count ?? 0;
|
|
72
|
+
if (attemptCount < minAttempts) {
|
|
73
|
+
return { ...empty, reason: 'insufficient_attempts' };
|
|
74
|
+
}
|
|
75
|
+
const scoreHistory = Array.isArray(entry.score_history) ? entry.score_history.filter((s) => typeof s === 'number') : [];
|
|
76
|
+
const rejectionHistory = Array.isArray(entry.rejection_history) ? entry.rejection_history.slice() : [];
|
|
77
|
+
if (scoreHistory.length < minAttempts) {
|
|
78
|
+
return { ...empty, reason: 'insufficient_score_history', scores: scoreHistory, rejection_reasons: rejectionHistory };
|
|
79
|
+
}
|
|
80
|
+
const recentScores = scoreHistory.slice(-minAttempts);
|
|
81
|
+
const maxScore = Math.max(...scoreHistory);
|
|
82
|
+
if (maxScore >= scoreCeiling) {
|
|
83
|
+
return {
|
|
84
|
+
stuck: false,
|
|
85
|
+
reason: 'score_ceiling_reached',
|
|
86
|
+
scores: recentScores,
|
|
87
|
+
rejection_reasons: rejectionHistory,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const v = variance(recentScores);
|
|
91
|
+
if (v < varianceThreshold) {
|
|
92
|
+
return {
|
|
93
|
+
stuck: true,
|
|
94
|
+
reason: `low_variance_${v.toFixed(2)}_below_${varianceThreshold}_after_${attemptCount}_attempts`,
|
|
95
|
+
scores: recentScores,
|
|
96
|
+
rejection_reasons: rejectionHistory,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
stuck: false,
|
|
101
|
+
reason: `variance_${v.toFixed(2)}_above_threshold`,
|
|
102
|
+
scores: recentScores,
|
|
103
|
+
rejection_reasons: rejectionHistory,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function safeParseJSON(text) {
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Try to extract a JSON object from surrounding prose
|
|
112
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
113
|
+
if (match) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(match[0]);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function validateDecision(raw) {
|
|
125
|
+
if (!raw || typeof raw !== 'object')
|
|
126
|
+
return null;
|
|
127
|
+
const obj = raw;
|
|
128
|
+
const action = obj.action;
|
|
129
|
+
const reasoning = obj.reasoning;
|
|
130
|
+
if (typeof action !== 'string' || typeof reasoning !== 'string')
|
|
131
|
+
return null;
|
|
132
|
+
if (action !== 'bump_tier' && action !== 'rephrase_context' && action !== 'escalate_human')
|
|
133
|
+
return null;
|
|
134
|
+
const decision = { action, reasoning };
|
|
135
|
+
if (action === 'bump_tier') {
|
|
136
|
+
const tier = obj.new_tier;
|
|
137
|
+
if (tier !== 'cloud-code' && tier !== 'apex')
|
|
138
|
+
return null;
|
|
139
|
+
decision.new_tier = tier;
|
|
140
|
+
if (typeof obj.budget_override === 'number')
|
|
141
|
+
decision.budget_override = obj.budget_override;
|
|
142
|
+
}
|
|
143
|
+
else if (action === 'rephrase_context') {
|
|
144
|
+
if (typeof obj.rephrased_context !== 'string' || obj.rephrased_context.trim().length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
decision.rephrased_context = obj.rephrased_context;
|
|
147
|
+
}
|
|
148
|
+
else if (action === 'escalate_human') {
|
|
149
|
+
if (typeof obj.telegram_message !== 'string' || obj.telegram_message.trim().length === 0)
|
|
150
|
+
return null;
|
|
151
|
+
decision.telegram_message = obj.telegram_message;
|
|
152
|
+
}
|
|
153
|
+
return decision;
|
|
154
|
+
}
|
|
155
|
+
function buildEscalationPrompt(taskId, taskTitle, taskContext, detection) {
|
|
156
|
+
const attempts = detection.scores
|
|
157
|
+
.slice()
|
|
158
|
+
.reverse()
|
|
159
|
+
.map((score, idx) => {
|
|
160
|
+
const reason = detection.rejection_reasons[detection.rejection_reasons.length - 1 - idx] ?? 'unknown';
|
|
161
|
+
return ` - attempt ${detection.scores.length - idx}: score=${score}, rejection="${reason}"`;
|
|
162
|
+
})
|
|
163
|
+
.join('\n');
|
|
164
|
+
return [
|
|
165
|
+
'You are the CEO+CTO escalation council for the Kognai multi-agent platform.',
|
|
166
|
+
`A coder agent is stuck on task "${taskId}" — "${taskTitle}".`,
|
|
167
|
+
'',
|
|
168
|
+
'Task context:',
|
|
169
|
+
taskContext,
|
|
170
|
+
'',
|
|
171
|
+
`Detector reason: ${detection.reason}`,
|
|
172
|
+
'Recent attempts (newest first):',
|
|
173
|
+
attempts || ' (none)',
|
|
174
|
+
'',
|
|
175
|
+
'Decide ONE action and return STRICT JSON with this shape:',
|
|
176
|
+
'{',
|
|
177
|
+
' "action": "bump_tier" | "rephrase_context" | "escalate_human",',
|
|
178
|
+
' "reasoning": "<short justification>",',
|
|
179
|
+
' "new_tier": "cloud-code" | "apex", // required iff action=bump_tier',
|
|
180
|
+
' "budget_override": <number, optional>, // only when action=bump_tier',
|
|
181
|
+
' "rephrased_context": "<full new context>",// required iff action=rephrase_context',
|
|
182
|
+
' "telegram_message": "<message for human>" // required iff action=escalate_human',
|
|
183
|
+
'}',
|
|
184
|
+
'',
|
|
185
|
+
'Return ONLY the JSON object — no commentary, no markdown fences.',
|
|
186
|
+
].join('\n');
|
|
187
|
+
}
|
|
188
|
+
function writeEscalationLog(swarmStateDir, taskId, decision, detection) {
|
|
189
|
+
try {
|
|
190
|
+
const dir = (0, node_path_1.join)(swarmStateDir, 'escalations');
|
|
191
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
192
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
193
|
+
const path = (0, node_path_1.join)(dir, `${taskId}-${stamp}.json`);
|
|
194
|
+
const payload = {
|
|
195
|
+
task_id: taskId,
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
detection,
|
|
198
|
+
decision,
|
|
199
|
+
};
|
|
200
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(payload, null, 2), 'utf8');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Best-effort logging; never throw.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function fallbackDecision(taskId, note) {
|
|
207
|
+
return {
|
|
208
|
+
action: 'escalate_human',
|
|
209
|
+
reasoning: `Stuck-task escalation API call failed (${note}) — manual review needed for ${taskId}`,
|
|
210
|
+
telegram_message: `⚠️ Coder agent stuck on ${taskId}. Escalation council unreachable (${note}). Please review.`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async function escalateStuckTask(sprintId, taskId, taskContext, taskTitle, detection, swarmStateDir = DEFAULT_SWARM_STATE_DIR) {
|
|
214
|
+
const model = process.env.STUCK_ESCALATION_MODEL ?? 'claude-sonnet-4-20250514';
|
|
215
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
216
|
+
if (!apiKey) {
|
|
217
|
+
const decision = fallbackDecision(taskId, 'missing ANTHROPIC_API_KEY');
|
|
218
|
+
writeEscalationLog(swarmStateDir, taskId, decision, detection);
|
|
219
|
+
return decision;
|
|
220
|
+
}
|
|
221
|
+
const prompt = buildEscalationPrompt(taskId, taskTitle, taskContext, detection);
|
|
222
|
+
let decision;
|
|
223
|
+
try {
|
|
224
|
+
// Raw HTTPS via anthropic-direct — codebase convention. There is no
|
|
225
|
+
// @anthropic-ai/sdk dependency available to scripts/ (it lives only in
|
|
226
|
+
// packages/kognai-build); the SDK import shipped by the swarm would throw
|
|
227
|
+
// at require-time. See scripts/lib/anthropic-direct.ts.
|
|
228
|
+
const response = await (0, anthropic_direct_1.callAnthropic)({
|
|
229
|
+
model,
|
|
230
|
+
system: 'You are the Kognai escalation council (CEO + CTO). Given a stuck task and its failure history, return ONLY a JSON EscalationDecision object.',
|
|
231
|
+
user: prompt,
|
|
232
|
+
max_tokens: 1500,
|
|
233
|
+
temperature: 0.2,
|
|
234
|
+
timeout_ms: Number.parseInt(process.env.ESCALATION_TIMEOUT_MS ?? '120000', 10),
|
|
235
|
+
});
|
|
236
|
+
const text = (response.content || '').trim();
|
|
237
|
+
const parsed = safeParseJSON(text);
|
|
238
|
+
const validated = validateDecision(parsed);
|
|
239
|
+
decision = validated ?? fallbackDecision(taskId, 'invalid council response');
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
const note = err instanceof Error ? err.message : 'unknown error';
|
|
243
|
+
decision = fallbackDecision(taskId, note);
|
|
244
|
+
}
|
|
245
|
+
// Note: sprintId is part of the calling sprint; we keep escalation log per task in shared dir.
|
|
246
|
+
void sprintId;
|
|
247
|
+
writeEscalationLog(swarmStateDir, taskId, decision, detection);
|
|
248
|
+
return decision;
|
|
249
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-contract-checker.ts — Sprint TICKET-005-RULE3
|
|
3
|
+
* Rule 3 (Task Contracts): autonomous sprint proposals must declare
|
|
4
|
+
* inputs, outputs, and success_criteria.
|
|
5
|
+
*
|
|
6
|
+
* This prevents vague "just implement something" briefs from reaching the coder.
|
|
7
|
+
*/
|
|
8
|
+
import type { SprintProposal } from './cto-gate-types';
|
|
9
|
+
export interface TaskContractResult {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
missing: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check that a sprint proposal includes all required contract fields.
|
|
15
|
+
* Each field must be present and a non-empty array.
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkTaskContracts(sprint: SprintProposal): TaskContractResult;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* task-contract-checker.ts — Sprint TICKET-005-RULE3
|
|
4
|
+
* Rule 3 (Task Contracts): autonomous sprint proposals must declare
|
|
5
|
+
* inputs, outputs, and success_criteria.
|
|
6
|
+
*
|
|
7
|
+
* This prevents vague "just implement something" briefs from reaching the coder.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.checkTaskContracts = checkTaskContracts;
|
|
11
|
+
const CONTRACT_FIELDS = [
|
|
12
|
+
'inputs',
|
|
13
|
+
'outputs',
|
|
14
|
+
'success_criteria',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Check that a sprint proposal includes all required contract fields.
|
|
18
|
+
* Each field must be present and a non-empty array.
|
|
19
|
+
*/
|
|
20
|
+
function checkTaskContracts(sprint) {
|
|
21
|
+
const missing = [];
|
|
22
|
+
for (const field of CONTRACT_FIELDS) {
|
|
23
|
+
const value = sprint[field];
|
|
24
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
25
|
+
missing.push(field);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { valid: missing.length === 0, missing };
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { TaskTarget, TIMEOUT_BUDGETS, TaskRoute, RoutingLogEntry, } from './types';
|
|
2
|
+
export { resolveRoute } from './router/resolve-route';
|
|
3
|
+
export { generateExecutionId } from './router/generate-execution-id';
|
|
4
|
+
import type { TaskTarget, TaskRoute, RoutingLogEntry } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Write a routing decision line to logs/routing/YYYY-MM-DD.jsonl
|
|
7
|
+
* and a compact idempotency line to logs/routing/executed.jsonl.
|
|
8
|
+
* Non-fatal: all errors are silently swallowed so routing logs never block execution.
|
|
9
|
+
*/
|
|
10
|
+
export declare function logRoutingDecision(entry: RoutingLogEntry): void;
|
|
11
|
+
/**
|
|
12
|
+
* Primary entry point: resolves a route for the given task target.
|
|
13
|
+
*
|
|
14
|
+
* @param target - The task target ('local' | 'cloud-code' | 'cloud-exec' | 'cloud-post')
|
|
15
|
+
* @returns The resolved route with provider, model, endpoint, and timeout
|
|
16
|
+
*/
|
|
17
|
+
export declare function getTaskRoute(target: TaskTarget): TaskRoute;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Kognai Task Router — Sprint-063
|
|
3
|
+
// Barrel export + logRoutingDecision utility
|
|
4
|
+
// DO NOT let linters revert this file — it must re-export from ./router/* subdir
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateExecutionId = exports.resolveRoute = exports.TIMEOUT_BUDGETS = void 0;
|
|
7
|
+
exports.logRoutingDecision = logRoutingDecision;
|
|
8
|
+
exports.getTaskRoute = getTaskRoute;
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
var types_1 = require("./types");
|
|
12
|
+
Object.defineProperty(exports, "TIMEOUT_BUDGETS", { enumerable: true, get: function () { return types_1.TIMEOUT_BUDGETS; } });
|
|
13
|
+
// Re-export from router/ subdirectory (NOT flat siblings)
|
|
14
|
+
var resolve_route_1 = require("./router/resolve-route");
|
|
15
|
+
Object.defineProperty(exports, "resolveRoute", { enumerable: true, get: function () { return resolve_route_1.resolveRoute; } });
|
|
16
|
+
var generate_execution_id_1 = require("./router/generate-execution-id");
|
|
17
|
+
Object.defineProperty(exports, "generateExecutionId", { enumerable: true, get: function () { return generate_execution_id_1.generateExecutionId; } });
|
|
18
|
+
const resolve_route_2 = require("./router/resolve-route");
|
|
19
|
+
/**
|
|
20
|
+
* Write a routing decision line to logs/routing/YYYY-MM-DD.jsonl
|
|
21
|
+
* and a compact idempotency line to logs/routing/executed.jsonl.
|
|
22
|
+
* Non-fatal: all errors are silently swallowed so routing logs never block execution.
|
|
23
|
+
*/
|
|
24
|
+
function logRoutingDecision(entry) {
|
|
25
|
+
try {
|
|
26
|
+
const logDir = (0, path_1.join)(process.cwd(), 'logs', 'routing');
|
|
27
|
+
(0, fs_1.mkdirSync)(logDir, { recursive: true });
|
|
28
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
29
|
+
const logged_at = new Date().toISOString();
|
|
30
|
+
const fullLine = JSON.stringify({ ...entry, logged_at });
|
|
31
|
+
const idempLine = JSON.stringify({
|
|
32
|
+
execution_id: entry.execution_id,
|
|
33
|
+
task_id: entry.task_id,
|
|
34
|
+
sprint_id: entry.sprint_id,
|
|
35
|
+
logged_at,
|
|
36
|
+
});
|
|
37
|
+
(0, fs_1.appendFileSync)((0, path_1.join)(logDir, `${today}.jsonl`), fullLine + '\n', 'utf-8');
|
|
38
|
+
(0, fs_1.appendFileSync)((0, path_1.join)(logDir, 'executed.jsonl'), idempLine + '\n', 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// non-fatal — routing log must never block execution
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Primary entry point: resolves a route for the given task target.
|
|
46
|
+
*
|
|
47
|
+
* @param target - The task target ('local' | 'cloud-code' | 'cloud-exec' | 'cloud-post')
|
|
48
|
+
* @returns The resolved route with provider, model, endpoint, and timeout
|
|
49
|
+
*/
|
|
50
|
+
function getTaskRoute(target) {
|
|
51
|
+
return (0, resolve_route_2.resolveRoute)(target);
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a deterministic execution ID from sprintId and taskId.
|
|
3
|
+
* Uses SHA256 hash of `${sprintId}:${taskId}` to create a consistent identifier.
|
|
4
|
+
*
|
|
5
|
+
* @param sprintId - The unique identifier for the sprint
|
|
6
|
+
* @param taskId - The unique identifier for the task
|
|
7
|
+
* @returns A 64-character hexadecimal SHA256 hash string
|
|
8
|
+
* @throws Error if sprintId or taskId is not provided
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateExecutionId(sprintId: string, taskId: string): string;
|