@wrongstack/core 0.309.1 → 0.310.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/dist/chronicle/index.js +113 -41
- package/dist/chronicle/metrics-ingest.d.ts +7 -0
- package/dist/chronicle/metrics-schema.d.ts +4 -1
- package/dist/chronicle/project-server.js +94 -38
- package/dist/chronicle/query.d.ts +1 -0
- package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
- package/dist/chronicle/types.d.ts +2 -0
- package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
- package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
- package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
- package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
- package/dist/coordination/fleet-status-tool.d.ts +1 -1
- package/dist/coordination/index.d.ts +5 -1
- package/dist/coordination/index.js +2061 -346
- package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
- package/dist/coordination/kanban-ops-port.d.ts +21 -0
- package/dist/coordination/mailbox-hooks.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +14 -9
- package/dist/core/context.d.ts +35 -44
- package/dist/core/conversation-state.d.ts +16 -52
- package/dist/core/index.js +53 -16
- package/dist/core/provider-runner.d.ts +2 -2
- package/dist/core/run-env.d.ts +7 -28
- package/dist/core/streaming-response-builder.d.ts +2 -2
- package/dist/execution/index.js +111 -153
- package/dist/goal/index.js +100 -22
- package/dist/hq/auth-store.d.ts +9 -0
- package/dist/hq/cost-bridge.d.ts +1 -1
- package/dist/hq/index.js +24 -2
- package/dist/index.d.ts +12 -5
- package/dist/index.js +27806 -33937
- package/dist/infrastructure/index.js +210 -132
- package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
- package/dist/infrastructure/token-counter.d.ts +5 -1
- package/dist/kernel/events/file-events.d.ts +6 -0
- package/dist/kernel/events/provider-events.d.ts +10 -7
- package/dist/kernel/events/session-events.d.ts +4 -4
- package/dist/kernel/events/tool-events.d.ts +12 -2
- package/dist/plugin/index.js +105 -35
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.js +47 -18
- package/dist/security/kanban-boundary.d.ts +1 -1
- package/dist/security/kanban-governance-port.d.ts +28 -0
- package/dist/session-catalog/index.js +2 -3
- package/dist/session-catalog/project-server.js +2 -3
- package/dist/session-catalog/protocol.d.ts +1 -1
- package/dist/session-catalog/registry.d.ts +1 -1
- package/dist/session-catalog/store-schema.d.ts +1 -1
- package/dist/session-catalog/store.d.ts +1 -1
- package/dist/storage/annotations-store.d.ts +1 -1
- package/dist/storage/board-store-port.d.ts +45 -0
- package/dist/storage/completed-work-checkpoint.d.ts +1 -1
- package/dist/storage/config-loader/types.d.ts +1 -1
- package/dist/storage/event-bus-port.d.ts +27 -0
- package/dist/storage/file-session-writer.d.ts +47 -5
- package/dist/storage/goal-coordination.d.ts +1 -1
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/index.d.ts +3 -4
- package/dist/storage/index.js +1063 -1459
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/queue-store.d.ts +1 -1
- package/dist/storage/replay-log-store.d.ts +1 -1
- package/dist/storage/session-recovery.d.ts +10 -0
- package/dist/storage/session-resume-validation.d.ts +13 -1
- package/dist/storage/session-store/events.d.ts +1 -1
- package/dist/storage/session-store/load-session-data.d.ts +1 -1
- package/dist/storage/session-store/rename-session.d.ts +1 -1
- package/dist/storage/session-store/resume-session.d.ts +3 -1
- package/dist/storage/session-store/session-store-index.d.ts +2 -1
- package/dist/storage/session-store/types.d.ts +1 -1
- package/dist/storage/session-store.d.ts +70 -0
- package/dist/storage/session-summary-tracker.d.ts +8 -0
- package/dist/storage/session-write-buffer.d.ts +31 -2
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/todos-checkpoint.d.ts +1 -1
- package/dist/storage/tool-audit-log.d.ts +1 -1
- package/dist/tasking/index.js +100 -22
- package/dist/tasking/task-tracker.d.ts +24 -1
- package/dist/types/compactor.d.ts +2 -2
- package/dist/types/context.d.ts +212 -0
- package/dist/types/conversation-state.d.ts +109 -0
- package/dist/types/error-handler.d.ts +2 -2
- package/dist/types/file-event-record.d.ts +6 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +17 -1
- package/dist/types/permission.d.ts +3 -3
- package/dist/types/plugin.d.ts +3 -3
- package/dist/types/provider-runner.d.ts +2 -2
- package/dist/types/provider.d.ts +10 -0
- package/dist/types/run-env.d.ts +32 -0
- package/dist/types/session.d.ts +3 -0
- package/dist/types/slash-command.d.ts +2 -2
- package/dist/types/token-counter.d.ts +30 -1
- package/dist/types/tool-executor.d.ts +2 -2
- package/dist/types/tool.d.ts +5 -5
- package/dist/utils/context-breakdown.d.ts +2 -2
- package/dist/utils/context-evidence.d.ts +12 -12
- package/dist/utils/crash-shield.d.ts +9 -0
- package/dist/utils/heap-watchdog.js +11 -3
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +72 -156
- package/dist/utils/regex-guard.d.ts +7 -30
- package/dist/utils/todos-format.d.ts +1 -1
- package/dist/utils/tree-kill.d.ts +2 -0
- package/dist/utils/tree-kill.js +1 -0
- package/instructions/coordination/subagent-baseline.md +10 -0
- package/instructions/system-lite.md +2 -0
- package/instructions/system-pro.md +40 -0
- package/instructions/system.md +15 -0
- package/package.json +6 -9
- package/dist/defaults/index.d.ts +0 -69
- package/dist/defaults/index.js +0 -38133
- /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
- /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
- /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
- /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
package/dist/execution/index.js
CHANGED
|
@@ -488,7 +488,9 @@ function hasToolUseBlock(message) {
|
|
|
488
488
|
return message?.role === "assistant" && Array.isArray(message.content) && message.content.some((block) => block.type === "tool_use");
|
|
489
489
|
}
|
|
490
490
|
var ConversationState = class {
|
|
491
|
+
/** Owning context; public for structural typing (Roadmap 10A). */
|
|
491
492
|
ctx;
|
|
493
|
+
/** Subscribers; public for structural typing (Roadmap 10A). */
|
|
492
494
|
listeners = /* @__PURE__ */ new Set();
|
|
493
495
|
_revision = 0;
|
|
494
496
|
constructor(ctx) {
|
|
@@ -543,9 +545,8 @@ var ConversationState = class {
|
|
|
543
545
|
}
|
|
544
546
|
/**
|
|
545
547
|
* How many of the oldest messages must be dropped to satisfy both retention
|
|
546
|
-
* caps — count ({@link
|
|
547
|
-
*
|
|
548
|
-
* fits, which is the overwhelmingly common case.
|
|
548
|
+
* caps — count and size (see {@link AgentContext.messageLimits}). Returns 0
|
|
549
|
+
* when the history already fits, which is the overwhelmingly common case.
|
|
549
550
|
*
|
|
550
551
|
* The size pass reads the per-message `_estTokens` cache populated at
|
|
551
552
|
* mutation time, so it is a sum over numbers rather than a re-walk of
|
|
@@ -553,9 +554,7 @@ var ConversationState = class {
|
|
|
553
554
|
* cap determines the starting index for the sum but does not gate it.
|
|
554
555
|
*/
|
|
555
556
|
overflowCount(arr) {
|
|
556
|
-
const
|
|
557
|
-
const maxMessages = contextClass.MAX_MESSAGES;
|
|
558
|
-
const maxMessageTokens = contextClass.MAX_MESSAGE_TOKENS;
|
|
557
|
+
const { maxMessages, maxMessageTokens } = this.ctx.messageLimits;
|
|
559
558
|
let drop = maxMessages > 0 ? Math.max(0, arr.length - maxMessages) : 0;
|
|
560
559
|
if (maxMessageTokens <= 0) return this.protocolSafeDropCount(arr, drop);
|
|
561
560
|
let total = 0;
|
|
@@ -826,6 +825,10 @@ var Context = class _Context {
|
|
|
826
825
|
* so storage operations can include it in `storage.*` events.
|
|
827
826
|
*/
|
|
828
827
|
traceId;
|
|
828
|
+
/** Logical provider request whose response produced the current tool calls. */
|
|
829
|
+
activeLogicalRequestId = void 0;
|
|
830
|
+
/** Content-addressed prompt composition for {@link activeLogicalRequestId}. */
|
|
831
|
+
activePromptManifestId = void 0;
|
|
829
832
|
/**
|
|
830
833
|
* Session id pinned to the currently-executing run. Set by `Agent.run()`
|
|
831
834
|
* at run start and cleared when the run ends. Event-emission sites must
|
|
@@ -851,6 +854,7 @@ var Context = class _Context {
|
|
|
851
854
|
return resolveEventSessionId(this);
|
|
852
855
|
}
|
|
853
856
|
/** Callbacks fired when `setWorkingDir()` changes the working directory. */
|
|
857
|
+
/** WorkingDir-change callbacks; public for structural typing (Roadmap 10A). */
|
|
854
858
|
_onWorkingDirChanged = [];
|
|
855
859
|
/**
|
|
856
860
|
* Serializes externally requested provider/model changes. Request creation
|
|
@@ -1106,7 +1110,16 @@ var Context = class _Context {
|
|
|
1106
1110
|
* For hooks that must survive across run boundaries (mailbox heartbeat,
|
|
1107
1111
|
* awareness polling, HQ publisher), prefer `registerAgentHook` instead.
|
|
1108
1112
|
*/
|
|
1113
|
+
/** Run-scoped abort hooks (drained by drainAbortHooks). Public for structural typing (Roadmap 10A). */
|
|
1109
1114
|
abortHooks = /* @__PURE__ */ new Set();
|
|
1115
|
+
/** Retention limits honoring runtime subclass overrides of the statics. */
|
|
1116
|
+
get messageLimits() {
|
|
1117
|
+
const cls = this.constructor;
|
|
1118
|
+
return Object.freeze({
|
|
1119
|
+
maxMessages: cls.MAX_MESSAGES,
|
|
1120
|
+
maxMessageTokens: cls.MAX_MESSAGE_TOKENS
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1110
1123
|
registerAbortHook(fn) {
|
|
1111
1124
|
this.abortHooks.add(fn);
|
|
1112
1125
|
return () => this.abortHooks.delete(fn);
|
|
@@ -1131,6 +1144,7 @@ var Context = class _Context {
|
|
|
1131
1144
|
* awareness polling interval, HQ publisher connection, and auto-compaction
|
|
1132
1145
|
* timer — resources that must survive from the first run to the last.
|
|
1133
1146
|
*/
|
|
1147
|
+
/** Session-lifetime teardown hooks (drained by drainAgentHooks). Public for structural typing (Roadmap 10A). */
|
|
1134
1148
|
agentHooks = /* @__PURE__ */ new Set();
|
|
1135
1149
|
registerAgentHook(fn) {
|
|
1136
1150
|
this.agentHooks.add(fn);
|
|
@@ -1311,6 +1325,9 @@ var Context = class _Context {
|
|
|
1311
1325
|
agentName: this.agentName,
|
|
1312
1326
|
provider: typeof this.provider === "object" ? this.provider.id : String(this.provider),
|
|
1313
1327
|
model: this.model,
|
|
1328
|
+
...this.activeLogicalRequestId ? { logicalRequestId: this.activeLogicalRequestId } : {},
|
|
1329
|
+
...this.activePromptManifestId ? { promptManifestId: this.activePromptManifestId } : {},
|
|
1330
|
+
provenanceConfidence: this.activeLogicalRequestId && this.activePromptManifestId ? "explicit" : "unknown",
|
|
1314
1331
|
toolName: input.toolName,
|
|
1315
1332
|
toolUseId: input.toolUseId,
|
|
1316
1333
|
scope,
|
|
@@ -1338,6 +1355,9 @@ var Context = class _Context {
|
|
|
1338
1355
|
agentName: this.agentName,
|
|
1339
1356
|
provider: record.provider,
|
|
1340
1357
|
model: record.model,
|
|
1358
|
+
...record.logicalRequestId ? { logicalRequestId: record.logicalRequestId } : {},
|
|
1359
|
+
...record.promptManifestId ? { promptManifestId: record.promptManifestId } : {},
|
|
1360
|
+
provenanceConfidence: record.provenanceConfidence,
|
|
1341
1361
|
toolName: input.toolName,
|
|
1342
1362
|
toolUseId: input.toolUseId,
|
|
1343
1363
|
scope,
|
|
@@ -3570,127 +3590,11 @@ function assertNever(x, message) {
|
|
|
3570
3590
|
}
|
|
3571
3591
|
|
|
3572
3592
|
// src/utils/regex-guard.ts
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
// Adjacent quantifiers: a++ a*+
|
|
3579
|
-
/[+*]{2,}/,
|
|
3580
|
-
// Quantifier on alternation with length 2+
|
|
3581
|
-
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
3582
|
-
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
3583
|
-
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
3584
|
-
];
|
|
3585
|
-
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
3586
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
3587
|
-
if (pattern[i] !== "(") continue;
|
|
3588
|
-
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
3589
|
-
let depth = 0;
|
|
3590
|
-
let inClass = false;
|
|
3591
|
-
let j = i;
|
|
3592
|
-
for (; j < pattern.length; j++) {
|
|
3593
|
-
const ch = pattern[j];
|
|
3594
|
-
if (ch === "\\") {
|
|
3595
|
-
j++;
|
|
3596
|
-
continue;
|
|
3597
|
-
}
|
|
3598
|
-
if (inClass) {
|
|
3599
|
-
if (ch === "]") inClass = false;
|
|
3600
|
-
continue;
|
|
3601
|
-
}
|
|
3602
|
-
if (ch === "[") {
|
|
3603
|
-
inClass = true;
|
|
3604
|
-
continue;
|
|
3605
|
-
}
|
|
3606
|
-
if (ch === "(") depth++;
|
|
3607
|
-
else if (ch === ")") {
|
|
3608
|
-
depth--;
|
|
3609
|
-
if (depth === 0) break;
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
if (j >= pattern.length) return false;
|
|
3613
|
-
const next = pattern[j + 1];
|
|
3614
|
-
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
3615
|
-
let inner = pattern.slice(i + 1, j);
|
|
3616
|
-
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
3617
|
-
const branches = [];
|
|
3618
|
-
let current = "";
|
|
3619
|
-
let d = 0;
|
|
3620
|
-
let cls = false;
|
|
3621
|
-
for (let k = 0; k < inner.length; k++) {
|
|
3622
|
-
const ch = inner[k];
|
|
3623
|
-
if (ch === "\\") {
|
|
3624
|
-
current += ch + (inner[k + 1] ?? "");
|
|
3625
|
-
k++;
|
|
3626
|
-
continue;
|
|
3627
|
-
}
|
|
3628
|
-
if (cls) {
|
|
3629
|
-
if (ch === "]") cls = false;
|
|
3630
|
-
current += ch;
|
|
3631
|
-
continue;
|
|
3632
|
-
}
|
|
3633
|
-
if (ch === "[") {
|
|
3634
|
-
cls = true;
|
|
3635
|
-
current += ch;
|
|
3636
|
-
continue;
|
|
3637
|
-
}
|
|
3638
|
-
if (ch === "(") d++;
|
|
3639
|
-
if (ch === ")") d--;
|
|
3640
|
-
if (ch === "|" && d === 0) {
|
|
3641
|
-
branches.push(current);
|
|
3642
|
-
current = "";
|
|
3643
|
-
continue;
|
|
3644
|
-
}
|
|
3645
|
-
current += ch;
|
|
3646
|
-
}
|
|
3647
|
-
branches.push(current);
|
|
3648
|
-
if (branches.length < 2) continue;
|
|
3649
|
-
for (let a = 0; a < branches.length; a++) {
|
|
3650
|
-
for (let b = a + 1; b < branches.length; b++) {
|
|
3651
|
-
const x = branches[a];
|
|
3652
|
-
const y = branches[b];
|
|
3653
|
-
if (x === "" || y === "") return true;
|
|
3654
|
-
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
3655
|
-
}
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
return false;
|
|
3659
|
-
}
|
|
3660
|
-
function compileUserRegex(pattern, flags) {
|
|
3661
|
-
if (typeof pattern !== "string") {
|
|
3662
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
3663
|
-
}
|
|
3664
|
-
if (pattern.length === 0) {
|
|
3665
|
-
return { ok: false, reason: "pattern is empty" };
|
|
3666
|
-
}
|
|
3667
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
3668
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
3669
|
-
}
|
|
3670
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
3671
|
-
if (rx.test(pattern)) {
|
|
3672
|
-
return {
|
|
3673
|
-
ok: false,
|
|
3674
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
3675
|
-
};
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
3679
|
-
return {
|
|
3680
|
-
ok: false,
|
|
3681
|
-
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
3682
|
-
};
|
|
3683
|
-
}
|
|
3684
|
-
try {
|
|
3685
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
3686
|
-
} catch (err) {
|
|
3687
|
-
return {
|
|
3688
|
-
ok: false,
|
|
3689
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
3690
|
-
};
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
3593
|
+
import {
|
|
3594
|
+
capSubject,
|
|
3595
|
+
compileUserRegex,
|
|
3596
|
+
MAX_SUBJECT_LEN
|
|
3597
|
+
} from "@wrongstack/primitives";
|
|
3694
3598
|
|
|
3695
3599
|
// src/execution/autonomous-runner.ts
|
|
3696
3600
|
var DoneConditionChecker = class {
|
|
@@ -5088,7 +4992,12 @@ function isContextOverflowShaped(err) {
|
|
|
5088
4992
|
const providerErr = err;
|
|
5089
4993
|
if (providerErr.kind === "context_overflow" || providerErr.status === 413) return true;
|
|
5090
4994
|
if (providerErr.status < 400) return false;
|
|
5091
|
-
const text = [
|
|
4995
|
+
const text = [
|
|
4996
|
+
providerErr.message,
|
|
4997
|
+
providerErr.body?.message,
|
|
4998
|
+
providerErr.body?.type,
|
|
4999
|
+
providerErr.body?.raw
|
|
5000
|
+
].filter(Boolean).join("\n");
|
|
5092
5001
|
return CONTEXT_OVERFLOW_RE.test(text);
|
|
5093
5002
|
}
|
|
5094
5003
|
function describeStatus(status, type) {
|
|
@@ -10393,27 +10302,39 @@ var DefaultErrorHandler = class {
|
|
|
10393
10302
|
import { execFile } from "node:child_process";
|
|
10394
10303
|
import { promisify } from "node:util";
|
|
10395
10304
|
|
|
10396
|
-
// src/storage/
|
|
10397
|
-
|
|
10305
|
+
// src/storage/board-store-port.ts
|
|
10306
|
+
var notWired = () => {
|
|
10307
|
+
throw new Error(
|
|
10308
|
+
"Kanban BoardStorePort is not wired \u2014 register the implementation at the CLI composition root (see setBoardStorePort)."
|
|
10309
|
+
);
|
|
10310
|
+
};
|
|
10311
|
+
var port = void 0;
|
|
10312
|
+
function boardStore() {
|
|
10313
|
+
return port ?? notWired();
|
|
10314
|
+
}
|
|
10315
|
+
function tryBoardStore() {
|
|
10316
|
+
return port ?? null;
|
|
10317
|
+
}
|
|
10398
10318
|
|
|
10399
10319
|
// src/storage/goal-kanban.ts
|
|
10400
|
-
import { addTask, createBoard, getBoard, listBoards, removeBoard } from "@wrongstack/kanban";
|
|
10401
10320
|
var GOAL_BOARD_TAG_PREFIX = "goal:";
|
|
10402
10321
|
async function findGoalKanbanBoard(projectRoot, boardId) {
|
|
10403
10322
|
if (!projectRoot || !boardId) return null;
|
|
10404
10323
|
try {
|
|
10405
|
-
return await getBoard(projectRoot, boardId);
|
|
10324
|
+
return await boardStore().getBoard(projectRoot, boardId);
|
|
10406
10325
|
} catch {
|
|
10407
10326
|
return null;
|
|
10408
10327
|
}
|
|
10409
10328
|
}
|
|
10410
10329
|
async function findGoalBoardByTag(projectRoot, goalFile) {
|
|
10411
10330
|
if (!projectRoot) return null;
|
|
10331
|
+
const store = tryBoardStore();
|
|
10332
|
+
if (!store) return null;
|
|
10412
10333
|
const tag = goalTag(goalFile);
|
|
10413
|
-
const boards = await listBoards(projectRoot);
|
|
10334
|
+
const boards = await store.listBoards(projectRoot);
|
|
10414
10335
|
const matched = boards.find((b) => b.tags?.includes(tag));
|
|
10415
10336
|
if (!matched) return null;
|
|
10416
|
-
return getBoard(projectRoot, matched.id).catch(() => null);
|
|
10337
|
+
return store.getBoard(projectRoot, matched.id).catch(() => null);
|
|
10417
10338
|
}
|
|
10418
10339
|
function goalTag(goalFile) {
|
|
10419
10340
|
const goal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, "-").slice(0, 24).toLowerCase();
|
|
@@ -10577,7 +10498,7 @@ async function refreshGoalKanban(projectRoot, goal, completed, resolvedBoard) {
|
|
|
10577
10498
|
const originMatch = task.origin?.system === "goal" && task.origin?.taskId ? originKeys.has(task.origin.taskId) : false;
|
|
10578
10499
|
const titleMatch = !task.origin?.taskId && completedTitles.has(normalizeTitle(task.title));
|
|
10579
10500
|
if (!originMatch && !titleMatch) continue;
|
|
10580
|
-
const result = await updateTask(
|
|
10501
|
+
const result = await boardStore().updateTask(
|
|
10581
10502
|
projectRoot,
|
|
10582
10503
|
board.id,
|
|
10583
10504
|
task.id,
|
|
@@ -22043,6 +21964,8 @@ function logToolSuccess(opts, ctx, use, toolName, durationMs, outputChars) {
|
|
|
22043
21964
|
id: use.id,
|
|
22044
21965
|
sessionId: ctx.session.id,
|
|
22045
21966
|
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21967
|
+
...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
|
|
21968
|
+
...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
|
|
22046
21969
|
agentId: ctx.agentId ?? "<unknown>",
|
|
22047
21970
|
durationMs,
|
|
22048
21971
|
outputChars
|
|
@@ -22067,6 +21990,8 @@ function logToolFailure(opts, ctx, use, toolName, durationMs, err) {
|
|
|
22067
21990
|
id: use.id,
|
|
22068
21991
|
sessionId: ctx.session.id,
|
|
22069
21992
|
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21993
|
+
...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
|
|
21994
|
+
...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
|
|
22070
21995
|
agentId: ctx.agentId ?? "<unknown>",
|
|
22071
21996
|
durationMs,
|
|
22072
21997
|
category,
|
|
@@ -22136,14 +22061,20 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
22136
22061
|
// src/security/kanban-boundary.ts
|
|
22137
22062
|
import { realpath } from "node:fs/promises";
|
|
22138
22063
|
import * as path17 from "node:path";
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
|
|
22142
|
-
|
|
22143
|
-
|
|
22144
|
-
|
|
22145
|
-
}
|
|
22146
|
-
var
|
|
22064
|
+
|
|
22065
|
+
// src/security/kanban-governance-port.ts
|
|
22066
|
+
var notWired2 = () => {
|
|
22067
|
+
throw new Error(
|
|
22068
|
+
"KanbanGovernancePort is not wired \u2014 register the implementation at the CLI composition root (see setKanbanGovernance)."
|
|
22069
|
+
);
|
|
22070
|
+
};
|
|
22071
|
+
var port2 = void 0;
|
|
22072
|
+
function kanbanGovernance() {
|
|
22073
|
+
return port2 ?? notWired2();
|
|
22074
|
+
}
|
|
22075
|
+
|
|
22076
|
+
// src/security/kanban-boundary.ts
|
|
22077
|
+
var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo", "nextsteps"]);
|
|
22147
22078
|
var PATH_KEYS = /* @__PURE__ */ new Set([
|
|
22148
22079
|
"path",
|
|
22149
22080
|
"paths",
|
|
@@ -22178,7 +22109,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
|
22178
22109
|
reason: "Kanban governance is mandatory before product mutation. Create a managed card, add its required details and executable acceptance criteria, then call kanban start_task to bind it to this run."
|
|
22179
22110
|
} : { decision: "allow" };
|
|
22180
22111
|
}
|
|
22181
|
-
const board = await readBoard(
|
|
22112
|
+
const board = await kanbanGovernance().readBoard(
|
|
22113
|
+
identity.policyRoot ?? ctx.projectRoot,
|
|
22114
|
+
identity.boardId
|
|
22115
|
+
);
|
|
22182
22116
|
if (!board) {
|
|
22183
22117
|
return governanceRequired ? {
|
|
22184
22118
|
decision: "block",
|
|
@@ -22204,22 +22138,39 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
|
22204
22138
|
taskId: identity.taskId
|
|
22205
22139
|
};
|
|
22206
22140
|
}
|
|
22207
|
-
const readiness = evaluateContractGraphReadiness(board, task.id);
|
|
22141
|
+
const readiness = kanbanGovernance().evaluateContractGraphReadiness(board, task.id);
|
|
22208
22142
|
if (!readiness.ready) {
|
|
22143
|
+
const issueLines = readiness.issues.map((issue, index) => ` ${index + 1}. ${issue.message}`).join("\n");
|
|
22209
22144
|
return {
|
|
22210
22145
|
decision: "block",
|
|
22211
|
-
reason:
|
|
22146
|
+
reason: `Active card failed contract-readiness check (${readiness.issues.length} issue(s)):
|
|
22147
|
+
` + issueLines + `
|
|
22148
|
+
Resolve the readiness issues, then call kanban start_task to retry.`,
|
|
22212
22149
|
boardId: board.id,
|
|
22213
22150
|
taskId: task.id,
|
|
22214
22151
|
readinessIssues: readiness.issues
|
|
22215
22152
|
};
|
|
22216
22153
|
}
|
|
22217
|
-
|
|
22218
|
-
|
|
22219
|
-
|
|
22154
|
+
const lifecycleStage = task.lifecycle?.currentStage;
|
|
22155
|
+
const assignmentStatus = task.assignment?.status;
|
|
22156
|
+
const lifecycleOk = lifecycleStage === "running";
|
|
22157
|
+
const assignmentOk = assignmentStatus === "running";
|
|
22158
|
+
if (!lifecycleOk || !assignmentOk) {
|
|
22159
|
+
const failed = [];
|
|
22160
|
+
let reassessmentNote = "";
|
|
22161
|
+
if (!lifecycleOk) {
|
|
22162
|
+
failed.push(`lifecycle.currentStage (got: ${lifecycleStage ?? "missing"}; want: 'running')`);
|
|
22163
|
+
}
|
|
22164
|
+
if (!assignmentOk) {
|
|
22165
|
+
failed.push(`assignment.status (got: ${assignmentStatus ?? "missing"}; want: 'running')`);
|
|
22166
|
+
if (lifecycleOk && (assignmentStatus === "queued" || assignmentStatus === void 0 || assignmentStatus === null)) {
|
|
22167
|
+
reassessmentNote = '\nNote: lifecycle is still "running" but the assignment was cleared \u2014 the automated kanban reassessment agent likely reset the binding. Re-issue kanban start_task to re-bind the leader to this card.';
|
|
22168
|
+
}
|
|
22169
|
+
}
|
|
22220
22170
|
return {
|
|
22221
22171
|
decision: "block",
|
|
22222
|
-
reason: `Active card
|
|
22172
|
+
reason: `Active card failed ${failed.length} governance check(s) before product mutation:
|
|
22173
|
+
` + failed.map((line, index) => ` ${index + 1}. ${line}`).join("\n") + reassessmentNote,
|
|
22223
22174
|
boardId: board.id,
|
|
22224
22175
|
taskId: task.id
|
|
22225
22176
|
};
|
|
@@ -22237,7 +22188,7 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
|
22237
22188
|
};
|
|
22238
22189
|
}
|
|
22239
22190
|
}
|
|
22240
|
-
const layers = resolveKanbanBoundaryLayers(board, task);
|
|
22191
|
+
const layers = kanbanGovernance().resolveKanbanBoundaryLayers(board, task);
|
|
22241
22192
|
if (layers.length === 0) return { decision: "allow" };
|
|
22242
22193
|
const caps = tool.capabilities ?? [];
|
|
22243
22194
|
const access2 = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
|
|
@@ -22245,10 +22196,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
|
22245
22196
|
const candidates = access2 ? await extractCandidatePaths(tool.name, input, ctx) : [];
|
|
22246
22197
|
let evaluation;
|
|
22247
22198
|
if (shellLike || access2 && candidates.length === 0) {
|
|
22248
|
-
evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
|
|
22199
|
+
evaluation = kanbanGovernance().evaluateKanbanBoundaryOpaque(layers, tool.name);
|
|
22249
22200
|
} else if (access2) {
|
|
22250
22201
|
const results = candidates.map(
|
|
22251
|
-
(candidate) => evaluateKanbanBoundaryPath(layers, candidate, access2)
|
|
22202
|
+
(candidate) => kanbanGovernance().evaluateKanbanBoundaryPath(layers, candidate, access2)
|
|
22252
22203
|
);
|
|
22253
22204
|
evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
|
|
22254
22205
|
} else {
|
|
@@ -22775,6 +22726,8 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
22775
22726
|
this.opts.events?.emit("permission.evaluated", {
|
|
22776
22727
|
sessionId: resolveEventSessionId(ctx),
|
|
22777
22728
|
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
22729
|
+
...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
|
|
22730
|
+
...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
|
|
22778
22731
|
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
22779
22732
|
name: tool.name,
|
|
22780
22733
|
id: use.id,
|
|
@@ -22926,6 +22879,9 @@ ${post.additionalContext}`;
|
|
|
22926
22879
|
agentName: ctx.agentName,
|
|
22927
22880
|
provider: typeof ctx.provider === "object" ? ctx.provider.id : String(ctx.provider),
|
|
22928
22881
|
model: ctx.model,
|
|
22882
|
+
...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
|
|
22883
|
+
...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
|
|
22884
|
+
provenanceConfidence: ctx.activeLogicalRequestId && ctx.activePromptManifestId ? "explicit" : "unknown",
|
|
22929
22885
|
toolName: tool.name,
|
|
22930
22886
|
toolUseId: use.id,
|
|
22931
22887
|
scope: ctx.currentKanbanTaskId ? "task" : "session",
|
|
@@ -23068,6 +23024,8 @@ ${preToolContext.text}` : preToolContext.text;
|
|
|
23068
23024
|
this.opts.events?.emit("tool.started", {
|
|
23069
23025
|
sessionId: resolveEventSessionId(ctx),
|
|
23070
23026
|
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
23027
|
+
...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
|
|
23028
|
+
...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
|
|
23071
23029
|
agentId: ctx.agentId,
|
|
23072
23030
|
agentName: ctx.agentName,
|
|
23073
23031
|
name: tool.name,
|
package/dist/goal/index.js
CHANGED
|
@@ -207,6 +207,13 @@ var TaskTracker = class _TaskTracker {
|
|
|
207
207
|
graph = null;
|
|
208
208
|
transitions = [];
|
|
209
209
|
listeners = [];
|
|
210
|
+
/**
|
|
211
|
+
* Re-entrancy guard for status cascades: true while an auto-block /
|
|
212
|
+
* auto-unblock cascade is running. Listener-driven mutations during a
|
|
213
|
+
* cascade still record their own primary transition — only their
|
|
214
|
+
* cascading is skipped (prevents unbounded recursion).
|
|
215
|
+
*/
|
|
216
|
+
cascading = false;
|
|
210
217
|
static MAX_TRANSITIONS = 1e4;
|
|
211
218
|
/**
|
|
212
219
|
* Subscribe to live task mutations (add / update / status change). Returns an
|
|
@@ -282,6 +289,28 @@ var TaskTracker = class _TaskTracker {
|
|
|
282
289
|
message: "No graph loaded",
|
|
283
290
|
code: ERROR_CODES.SDD_INVALID_STATE
|
|
284
291
|
});
|
|
292
|
+
if (!this.graph.nodes.has(from) || !this.graph.nodes.has(to)) {
|
|
293
|
+
throw new SddError({
|
|
294
|
+
message: `Cannot add ${type} edge ${from} -> ${to}: endpoint task is not in the graph`,
|
|
295
|
+
code: ERROR_CODES.SDD_NOT_READY,
|
|
296
|
+
context: { from, to, type }
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (from === to) {
|
|
300
|
+
throw new SddError({
|
|
301
|
+
message: `Cannot add ${type} edge: self-loop on task ${from}`,
|
|
302
|
+
code: ERROR_CODES.SDD_INVALID_STATE,
|
|
303
|
+
context: { nodeId: from, type }
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
if (this.graph.edges.some((e) => e.from === from && e.to === to && e.type === type)) return;
|
|
307
|
+
if (type === "depends_on" && this.dependsOnTransitively(from, to, /* @__PURE__ */ new Set())) {
|
|
308
|
+
throw new SddError({
|
|
309
|
+
message: `Cannot add depends_on edge ${from} -> ${to}: it would create a dependency cycle`,
|
|
310
|
+
code: ERROR_CODES.SDD_INVALID_STATE,
|
|
311
|
+
context: { from, to }
|
|
312
|
+
});
|
|
313
|
+
}
|
|
285
314
|
this.graph.edges.push({
|
|
286
315
|
id: crypto.randomUUID(),
|
|
287
316
|
from,
|
|
@@ -382,7 +411,7 @@ var TaskTracker = class _TaskTracker {
|
|
|
382
411
|
if (status === "in_progress") {
|
|
383
412
|
node.startedAt = now;
|
|
384
413
|
}
|
|
385
|
-
this.transitions.push({ from, to: status, timestamp: now, reason });
|
|
414
|
+
this.transitions.push({ nodeId: id, from, to: status, timestamp: now, reason });
|
|
386
415
|
if (this.transitions.length > _TaskTracker.MAX_TRANSITIONS) {
|
|
387
416
|
this.transitions.splice(0, this.transitions.length - _TaskTracker.MAX_TRANSITIONS);
|
|
388
417
|
}
|
|
@@ -398,7 +427,7 @@ var TaskTracker = class _TaskTracker {
|
|
|
398
427
|
type: "status_changed",
|
|
399
428
|
nodeId: id,
|
|
400
429
|
node,
|
|
401
|
-
transition: { from, to: status, timestamp: now, reason }
|
|
430
|
+
transition: { nodeId: id, from, to: status, timestamp: now, reason }
|
|
402
431
|
});
|
|
403
432
|
}
|
|
404
433
|
updateNode(id, patch) {
|
|
@@ -489,40 +518,89 @@ var TaskTracker = class _TaskTracker {
|
|
|
489
518
|
}
|
|
490
519
|
return computeTaskProgress(this.graph);
|
|
491
520
|
}
|
|
492
|
-
|
|
493
|
-
|
|
521
|
+
/**
|
|
522
|
+
* Recorded transitions in insertion order (oldest first — the array is
|
|
523
|
+
* trimmed from the front). With `taskId`, only that task's transitions are
|
|
524
|
+
* returned, still oldest-first; a consumer wanting newest-first reverses
|
|
525
|
+
* the result. Per-task filtering is possible because every TaskTransition
|
|
526
|
+
* carries `nodeId` (BIZ-005).
|
|
527
|
+
*/
|
|
528
|
+
getTransitions(taskId) {
|
|
529
|
+
return taskId === void 0 ? [...this.transitions] : this.transitions.filter((t) => t.nodeId === taskId);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Record a cascade-driven status change (BIZ-003): mutate the node, push a
|
|
533
|
+
* TaskTransition, persist, and notify subscribers — the same effects an
|
|
534
|
+
* explicit updateNodeStatus produces — so the board projector's live
|
|
535
|
+
* snapshot and the transition log observe auto-block/auto-unblock.
|
|
536
|
+
*/
|
|
537
|
+
recordCascadeStatusChange(nodeId, node, to, reason) {
|
|
538
|
+
const from = node.status;
|
|
539
|
+
if (from === to) return;
|
|
540
|
+
const now = Date.now();
|
|
541
|
+
node.status = to;
|
|
542
|
+
node.updatedAt = now;
|
|
543
|
+
if (this.graph) this.graph.updatedAt = now;
|
|
544
|
+
this.transitions.push({ nodeId, from, to, timestamp: now, reason });
|
|
545
|
+
if (this.transitions.length > _TaskTracker.MAX_TRANSITIONS) {
|
|
546
|
+
this.transitions.splice(0, this.transitions.length - _TaskTracker.MAX_TRANSITIONS);
|
|
547
|
+
}
|
|
548
|
+
this.persist();
|
|
549
|
+
this.notifyChange({
|
|
550
|
+
type: "status_changed",
|
|
551
|
+
nodeId,
|
|
552
|
+
node,
|
|
553
|
+
transition: { nodeId, from, to, timestamp: now, reason }
|
|
554
|
+
});
|
|
494
555
|
}
|
|
495
556
|
unblockDependents(completedId) {
|
|
496
|
-
if (!this.graph) return;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
557
|
+
if (!this.graph || this.cascading) return;
|
|
558
|
+
this.cascading = true;
|
|
559
|
+
try {
|
|
560
|
+
const dependents = this.getDependents(completedId);
|
|
561
|
+
for (const depId of dependents) {
|
|
562
|
+
const dep = this.graph.nodes.get(depId);
|
|
563
|
+
if (dep?.status !== "blocked") continue;
|
|
501
564
|
const remainingBlockers = this.getBlockers(depId);
|
|
502
565
|
const allUnblocked = remainingBlockers.every((id) => {
|
|
503
566
|
const blocker = this.graph?.nodes.get(id);
|
|
504
567
|
return blocker?.status === "completed" || blocker?.status === "failed";
|
|
505
568
|
});
|
|
506
569
|
if (allUnblocked) {
|
|
507
|
-
|
|
508
|
-
|
|
570
|
+
this.recordCascadeStatusChange(
|
|
571
|
+
depId,
|
|
572
|
+
dep,
|
|
573
|
+
"pending",
|
|
574
|
+
`auto-unblocked: blocker ${completedId} reached a terminal state`
|
|
575
|
+
);
|
|
509
576
|
}
|
|
510
577
|
}
|
|
578
|
+
} finally {
|
|
579
|
+
this.cascading = false;
|
|
511
580
|
}
|
|
512
581
|
}
|
|
513
582
|
checkAndBlockIfNeeded(taskId) {
|
|
514
|
-
if (!this.graph) return;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (
|
|
523
|
-
node
|
|
524
|
-
|
|
583
|
+
if (!this.graph || this.cascading) return;
|
|
584
|
+
this.cascading = true;
|
|
585
|
+
try {
|
|
586
|
+
const blockers = this.getBlockers(taskId);
|
|
587
|
+
const incomplete = blockers.filter((id) => {
|
|
588
|
+
const blocker = this.graph?.nodes.get(id);
|
|
589
|
+
return blocker?.status !== "completed" && blocker?.status !== "failed";
|
|
590
|
+
});
|
|
591
|
+
if (incomplete.length > 0) {
|
|
592
|
+
const node = this.graph.nodes.get(taskId);
|
|
593
|
+
if (node) {
|
|
594
|
+
this.recordCascadeStatusChange(
|
|
595
|
+
taskId,
|
|
596
|
+
node,
|
|
597
|
+
"blocked",
|
|
598
|
+
`auto-blocked: incomplete blocker(s) ${incomplete.join(", ")}`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
525
601
|
}
|
|
602
|
+
} finally {
|
|
603
|
+
this.cascading = false;
|
|
526
604
|
}
|
|
527
605
|
}
|
|
528
606
|
/**
|
package/dist/hq/auth-store.d.ts
CHANGED
|
@@ -167,6 +167,15 @@ export interface HqAuthFile {
|
|
|
167
167
|
* reveal unused codes. Each code is consumed on first successful use.
|
|
168
168
|
*/
|
|
169
169
|
totpRecoveryCodes?: string[];
|
|
170
|
+
/**
|
|
171
|
+
* Highest TOTP time-step counter already consumed by a successful 2FA
|
|
172
|
+
* verification (SEC-002: persisted to prevent replay after restart).
|
|
173
|
+
* Written by the 2FA verification route (`totp-routes.ts`) — not the
|
|
174
|
+
* initial password check — and read there to reject any counter <= this
|
|
175
|
+
* value, so an observed code cannot be replayed within its validity
|
|
176
|
+
* window even across a server restart.
|
|
177
|
+
*/
|
|
178
|
+
totpLastUsedCounter?: number;
|
|
170
179
|
/**
|
|
171
180
|
* Operator-configured alert-rule thresholds. When present, these override
|
|
172
181
|
* the built-in defaults for the alert engine (cost ceiling, stale-machine
|
package/dist/hq/cost-bridge.d.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* @module hq/cost-bridge
|
|
17
17
|
*/
|
|
18
18
|
import type { EventBus } from '../kernel/events.js';
|
|
19
|
-
import type { HqEventEnvelope } from './protocol.js';
|
|
20
19
|
import { type BridgeContextOptions } from './bridge-context.js';
|
|
20
|
+
import type { HqEventEnvelope } from './protocol.js';
|
|
21
21
|
export interface CostTelemetryBridgeOptions extends BridgeContextOptions {
|
|
22
22
|
/** Local EventBus emitting `token.accounted`. */
|
|
23
23
|
events: EventBus;
|