@wrongstack/core 0.289.0 → 0.291.1
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/boot.d.ts.map +1 -1
- package/dist/chronicle/domain-adapter.d.ts.map +1 -1
- package/dist/chronicle/index.d.ts +1 -1
- package/dist/chronicle/index.d.ts.map +1 -1
- package/dist/chronicle/index.js +964 -245
- package/dist/chronicle/index.js.map +3 -3
- package/dist/chronicle/journal.d.ts +35 -3
- package/dist/chronicle/journal.d.ts.map +1 -1
- package/dist/chronicle/query.d.ts +8 -5
- package/dist/chronicle/query.d.ts.map +1 -1
- package/dist/chronicle/tool-adapter.d.ts.map +1 -1
- package/dist/coordination/agent-subagent-runner.d.ts +6 -0
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +2 -1
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/collab-debug.d.ts +0 -9
- package/dist/coordination/collab-debug.d.ts.map +1 -1
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
- package/dist/coordination/dep-watcher.d.ts +2 -1
- package/dist/coordination/dep-watcher.d.ts.map +1 -1
- package/dist/coordination/director/director-collab.d.ts +1 -1
- package/dist/coordination/director/director-collab.d.ts.map +1 -1
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +19 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-event-validation.d.ts +0 -7
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/fleet-supervisor.d.ts +1 -1
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +0 -44
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -2
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +733 -111
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +2 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/large-answer-store.d.ts +0 -6
- package/dist/coordination/large-answer-store.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +35 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-tool.d.ts +1 -1
- package/dist/coordination/mailbox-tool.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +151 -0
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/provider-status-tracker.d.ts +27 -2
- package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-budget.d.ts +2 -1
- package/dist/coordination/subagent-budget.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +2 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/fallback-profile-manager.d.ts +1 -1
- package/dist/core/fallback-profile-manager.d.ts.map +1 -1
- package/dist/core/fleet-pulse.d.ts +2 -1
- package/dist/core/fleet-pulse.d.ts.map +1 -1
- package/dist/core/iteration-limit.d.ts +2 -1
- package/dist/core/iteration-limit.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +11 -1
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/model-availability-calendar.d.ts +27 -0
- package/dist/core/model-availability-calendar.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +2 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.js +1459 -560
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +3 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +8 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +2 -2
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.js +643 -134
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +0 -16
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts.map +1 -1
- package/dist/execution/retry-policy.d.ts +0 -13
- package/dist/execution/retry-policy.d.ts.map +1 -1
- package/dist/execution/skill-loader.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/index.js +39 -1
- package/dist/hq/index.js.map +3 -3
- package/dist/index.d.ts +32 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22263 -19999
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +59 -5
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/agent-events.d.ts +2 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -1
- package/dist/kernel/events/file-events.d.ts +46 -0
- package/dist/kernel/events/file-events.d.ts.map +1 -1
- package/dist/kernel/events/memory-events.d.ts +9 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/provider-events.d.ts +2 -1
- package/dist/kernel/events/provider-events.d.ts.map +1 -1
- package/dist/kernel/events/tool-events.d.ts +25 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/plugins/auto-review-plugin.d.ts +10 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
- package/dist/plugins/chimera-plugin.d.ts +5 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +317 -9
- package/dist/security/index.js.map +4 -4
- package/dist/security/kanban-boundary.d.ts +10 -0
- package/dist/security/kanban-boundary.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +8 -1
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/storage/config-loader.d.ts +15 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +627 -428
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +49 -4
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/memory-graph-backend.d.ts +11 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -1
- package/dist/storage/provider-config-watcher.d.ts +17 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-event-bridge.d.ts.map +1 -1
- package/dist/tools/fallback-manage-tools.d.ts.map +1 -1
- package/dist/tools/index.js +166 -26
- package/dist/tools/index.js.map +3 -3
- package/dist/types/config.d.ts +12 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/file-event-record.d.ts +68 -0
- package/dist/types/file-event-record.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/permission.d.ts +38 -0
- package/dist/types/permission.d.ts.map +1 -1
- package/dist/types/provider.d.ts +1 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/session.d.ts +27 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/utils/config-backup.d.ts +20 -0
- package/dist/utils/config-backup.d.ts.map +1 -0
- package/dist/utils/index.d.ts +12 -11
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +436 -290
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +12 -0
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.d.ts.map +1 -1
- package/dist/utils/term.d.ts +6 -0
- package/dist/utils/term.d.ts.map +1 -1
- package/dist/utils/tool-wire-compact.d.ts +9 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +21 -1
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/instructions/agents/ios.md +113 -0
- package/instructions/llm/memory-consolidator.md +4 -12
- package/instructions/system.md +4 -1
- package/package.json +3 -3
- package/skills/auto-review/SKILL.md +176 -0
- package/skills/mnemosyne/SKILL.md +156 -0
- package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
package/dist/execution/index.js
CHANGED
|
@@ -32,8 +32,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
let mode;
|
|
34
34
|
try {
|
|
35
|
-
const
|
|
36
|
-
mode =
|
|
35
|
+
const stat4 = await fs.stat(targetPath);
|
|
36
|
+
mode = stat4.mode & 511;
|
|
37
37
|
} catch {
|
|
38
38
|
mode = opts.mode;
|
|
39
39
|
}
|
|
@@ -76,7 +76,7 @@ async function renameWithRetry(from, to) {
|
|
|
76
76
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
77
77
|
throw err;
|
|
78
78
|
}
|
|
79
|
-
await new Promise((
|
|
79
|
+
await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
throw lastErr;
|
|
@@ -181,10 +181,10 @@ function validateAgainstSchema(value, schema) {
|
|
|
181
181
|
return { ok: errors.length === 0, errors };
|
|
182
182
|
}
|
|
183
183
|
var MAX_SCHEMA_DEPTH = 64;
|
|
184
|
-
function walk(value, schema,
|
|
184
|
+
function walk(value, schema, path12, errors, depth) {
|
|
185
185
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
186
186
|
errors.push({
|
|
187
|
-
path:
|
|
187
|
+
path: path12 || "<root>",
|
|
188
188
|
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
189
189
|
});
|
|
190
190
|
return;
|
|
@@ -192,7 +192,7 @@ function walk(value, schema, path11, errors, depth) {
|
|
|
192
192
|
if (schema.enum !== void 0) {
|
|
193
193
|
if (!enumIncludes(schema.enum, value)) {
|
|
194
194
|
errors.push({
|
|
195
|
-
path:
|
|
195
|
+
path: path12 || "<root>",
|
|
196
196
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
197
197
|
});
|
|
198
198
|
return;
|
|
@@ -201,7 +201,7 @@ function walk(value, schema, path11, errors, depth) {
|
|
|
201
201
|
if (typeof schema.type === "string") {
|
|
202
202
|
if (!checkType(value, schema.type)) {
|
|
203
203
|
errors.push({
|
|
204
|
-
path:
|
|
204
|
+
path: path12 || "<root>",
|
|
205
205
|
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
206
206
|
});
|
|
207
207
|
return;
|
|
@@ -213,7 +213,7 @@ function walk(value, schema, path11, errors, depth) {
|
|
|
213
213
|
if (!(req in obj)) {
|
|
214
214
|
const expected = schema.properties?.[req]?.type;
|
|
215
215
|
errors.push({
|
|
216
|
-
path: joinPath(
|
|
216
|
+
path: joinPath(path12, req),
|
|
217
217
|
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
218
218
|
});
|
|
219
219
|
}
|
|
@@ -221,14 +221,14 @@ function walk(value, schema, path11, errors, depth) {
|
|
|
221
221
|
if (schema.properties) {
|
|
222
222
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
223
223
|
if (key in obj) {
|
|
224
|
-
walk(obj[key], subSchema, joinPath(
|
|
224
|
+
walk(obj[key], subSchema, joinPath(path12, key), errors, depth + 1);
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
230
230
|
for (let i = 0; i < value.length; i++) {
|
|
231
|
-
walk(value[i], schema.items, `${
|
|
231
|
+
walk(value[i], schema.items, `${path12}[${i}]`, errors, depth + 1);
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
}
|
|
@@ -445,9 +445,23 @@ function mapContent(msg, fn) {
|
|
|
445
445
|
}
|
|
446
446
|
return { ...msg, content: next };
|
|
447
447
|
}
|
|
448
|
+
function hasMeaningfulContent(content) {
|
|
449
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
450
|
+
for (const block of content) {
|
|
451
|
+
if (block.type === "text") {
|
|
452
|
+
if (block.text.trim().length > 0) return true;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (block.type === "thinking") {
|
|
456
|
+
if (block.thinking.trim().length > 0 || block.signature) return true;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
448
463
|
function isEmptyMessage(msg) {
|
|
449
|
-
|
|
450
|
-
return msg.content.length === 0;
|
|
464
|
+
return !hasMeaningfulContent(msg.content);
|
|
451
465
|
}
|
|
452
466
|
|
|
453
467
|
// src/utils/regex-guard.ts
|
|
@@ -488,7 +502,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
488
502
|
|
|
489
503
|
// src/utils/safe-json.ts
|
|
490
504
|
function safeParse(input, maxBytes = 5e6) {
|
|
491
|
-
if (input
|
|
505
|
+
if (Buffer.byteLength(input, "utf8") > maxBytes) {
|
|
492
506
|
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
493
507
|
}
|
|
494
508
|
try {
|
|
@@ -501,84 +515,21 @@ function safeParse(input, maxBytes = 5e6) {
|
|
|
501
515
|
}
|
|
502
516
|
}
|
|
503
517
|
|
|
518
|
+
// src/utils/sleep.ts
|
|
519
|
+
function sleep(ms) {
|
|
520
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
521
|
+
}
|
|
522
|
+
|
|
504
523
|
// src/utils/slug.ts
|
|
505
524
|
function slugify(name, fallback = "prompt", maxLen = 64) {
|
|
506
525
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
|
|
507
526
|
}
|
|
508
527
|
|
|
509
|
-
// src/utils/ulid.ts
|
|
510
|
-
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
511
|
-
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
512
|
-
var ENCODING_LEN = ENCODING.length;
|
|
513
|
-
var TIME_LEN = 10;
|
|
514
|
-
var RANDOM_LEN = 16;
|
|
515
|
-
function encodeTime(now, len) {
|
|
516
|
-
let mod;
|
|
517
|
-
let str = "";
|
|
518
|
-
for (let i = len - 1; i >= 0; i--) {
|
|
519
|
-
mod = now % ENCODING_LEN;
|
|
520
|
-
str = ENCODING[mod] + str;
|
|
521
|
-
now = (now - mod) / ENCODING_LEN;
|
|
522
|
-
}
|
|
523
|
-
return str;
|
|
524
|
-
}
|
|
525
|
-
function encodeRandom(len) {
|
|
526
|
-
const bytes = randomBytes2(len);
|
|
527
|
-
let str = "";
|
|
528
|
-
for (let i = 0; i < len; i++) {
|
|
529
|
-
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
530
|
-
}
|
|
531
|
-
return str;
|
|
532
|
-
}
|
|
533
|
-
function ulid(seedTime = Date.now()) {
|
|
534
|
-
return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// src/utils/sleep.ts
|
|
538
|
-
function sleep(ms) {
|
|
539
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
540
|
-
}
|
|
541
|
-
|
|
542
528
|
// src/utils/string.ts
|
|
543
529
|
function truncate(s, max) {
|
|
544
530
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
545
531
|
}
|
|
546
532
|
|
|
547
|
-
// src/utils/tool-subject.ts
|
|
548
|
-
var GLOB_METACHARACTERS = /[*?[\]]/g;
|
|
549
|
-
function escapeGlobSubject(value) {
|
|
550
|
-
return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
|
|
551
|
-
}
|
|
552
|
-
function normalizePathSubject(value) {
|
|
553
|
-
return escapeGlobSubject(value.replace(/\\/g, "/"));
|
|
554
|
-
}
|
|
555
|
-
function isPathSubjectKey(subjectKey) {
|
|
556
|
-
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
557
|
-
}
|
|
558
|
-
function subjectForToolInput(toolName, input, subjectKey) {
|
|
559
|
-
if (!input || typeof input !== "object") return void 0;
|
|
560
|
-
const obj = input;
|
|
561
|
-
if (subjectKey) {
|
|
562
|
-
const value = obj[subjectKey];
|
|
563
|
-
if (typeof value === "string") {
|
|
564
|
-
return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (toolName === "bash" && typeof obj.command === "string") {
|
|
568
|
-
return escapeGlobSubject(obj.command);
|
|
569
|
-
}
|
|
570
|
-
if (typeof obj.path === "string") {
|
|
571
|
-
return normalizePathSubject(obj.path);
|
|
572
|
-
}
|
|
573
|
-
if (typeof obj.url === "string") {
|
|
574
|
-
return escapeGlobSubject(obj.url);
|
|
575
|
-
}
|
|
576
|
-
if (typeof obj.name === "string") {
|
|
577
|
-
return escapeGlobSubject(obj.name);
|
|
578
|
-
}
|
|
579
|
-
return void 0;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
533
|
// src/utils/tool-wire-compact.ts
|
|
583
534
|
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
584
535
|
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
@@ -595,9 +546,11 @@ function compactToolDefinitionForWire(tool, opts = {}) {
|
|
|
595
546
|
tool.description ?? "",
|
|
596
547
|
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
597
548
|
),
|
|
598
|
-
inputSchema:
|
|
599
|
-
|
|
600
|
-
|
|
549
|
+
inputSchema: normalizeTopLevelToolSchema(
|
|
550
|
+
compactSchemaDescriptions(
|
|
551
|
+
tool.inputSchema,
|
|
552
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
553
|
+
)
|
|
601
554
|
)
|
|
602
555
|
};
|
|
603
556
|
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
@@ -605,6 +558,44 @@ function compactToolDefinitionForWire(tool, opts = {}) {
|
|
|
605
558
|
}
|
|
606
559
|
return compact;
|
|
607
560
|
}
|
|
561
|
+
function normalizeTopLevelToolSchema(schema) {
|
|
562
|
+
const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
|
|
563
|
+
(entry) => Array.isArray(entry.branches)
|
|
564
|
+
);
|
|
565
|
+
if (combinators.length === 0) return schema;
|
|
566
|
+
const out = { ...schema, type: "object" };
|
|
567
|
+
delete out["oneOf"];
|
|
568
|
+
delete out["anyOf"];
|
|
569
|
+
delete out["allOf"];
|
|
570
|
+
const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
|
|
571
|
+
let required = stringSet(schema["required"]);
|
|
572
|
+
for (const { keyword, branches } of combinators) {
|
|
573
|
+
const objectBranches = branches.filter(isRecord);
|
|
574
|
+
for (const branch of objectBranches) {
|
|
575
|
+
if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
|
|
576
|
+
}
|
|
577
|
+
const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
|
|
578
|
+
if (keyword === "allOf") {
|
|
579
|
+
for (const fields of branchRequired) for (const field of fields) required.add(field);
|
|
580
|
+
} else if (branchRequired.length > 0) {
|
|
581
|
+
const common = new Set(
|
|
582
|
+
[...branchRequired[0]].filter(
|
|
583
|
+
(field) => branchRequired.slice(1).every((fields) => fields.has(field))
|
|
584
|
+
)
|
|
585
|
+
);
|
|
586
|
+
required = /* @__PURE__ */ new Set([...required, ...common]);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
out["properties"] = properties;
|
|
590
|
+
if (required.size > 0) out["required"] = [...required];
|
|
591
|
+
else delete out["required"];
|
|
592
|
+
return out;
|
|
593
|
+
}
|
|
594
|
+
function stringSet(value) {
|
|
595
|
+
return new Set(
|
|
596
|
+
Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
|
|
597
|
+
);
|
|
598
|
+
}
|
|
608
599
|
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
609
600
|
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
610
601
|
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
@@ -1499,6 +1490,69 @@ function resolveToolResultRenderMode(modes, toolName) {
|
|
|
1499
1490
|
return normalizeToolResultRenderMode(modes?.[toolName]) ?? DEFAULT_TOOL_RESULT_RENDER_MODE;
|
|
1500
1491
|
}
|
|
1501
1492
|
|
|
1493
|
+
// src/utils/tool-subject.ts
|
|
1494
|
+
var GLOB_METACHARACTERS = /[*?[\]]/g;
|
|
1495
|
+
function escapeGlobSubject(value) {
|
|
1496
|
+
return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
|
|
1497
|
+
}
|
|
1498
|
+
function normalizePathSubject(value) {
|
|
1499
|
+
return escapeGlobSubject(value.replace(/\\/g, "/"));
|
|
1500
|
+
}
|
|
1501
|
+
function isPathSubjectKey(subjectKey) {
|
|
1502
|
+
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
1503
|
+
}
|
|
1504
|
+
function subjectForToolInput(toolName, input, subjectKey) {
|
|
1505
|
+
if (!input || typeof input !== "object") return void 0;
|
|
1506
|
+
const obj = input;
|
|
1507
|
+
if (subjectKey) {
|
|
1508
|
+
const value = obj[subjectKey];
|
|
1509
|
+
if (typeof value === "string") {
|
|
1510
|
+
return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (toolName === "bash" && typeof obj.command === "string") {
|
|
1514
|
+
return escapeGlobSubject(obj.command);
|
|
1515
|
+
}
|
|
1516
|
+
if (typeof obj.path === "string") {
|
|
1517
|
+
return normalizePathSubject(obj.path);
|
|
1518
|
+
}
|
|
1519
|
+
if (typeof obj.url === "string") {
|
|
1520
|
+
return escapeGlobSubject(obj.url);
|
|
1521
|
+
}
|
|
1522
|
+
if (typeof obj.name === "string") {
|
|
1523
|
+
return escapeGlobSubject(obj.name);
|
|
1524
|
+
}
|
|
1525
|
+
return void 0;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/utils/ulid.ts
|
|
1529
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1530
|
+
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
1531
|
+
var ENCODING_LEN = ENCODING.length;
|
|
1532
|
+
var TIME_LEN = 10;
|
|
1533
|
+
var RANDOM_LEN = 16;
|
|
1534
|
+
function encodeTime(now, len) {
|
|
1535
|
+
let mod;
|
|
1536
|
+
let str = "";
|
|
1537
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
1538
|
+
mod = now % ENCODING_LEN;
|
|
1539
|
+
str = ENCODING[mod] + str;
|
|
1540
|
+
now = (now - mod) / ENCODING_LEN;
|
|
1541
|
+
}
|
|
1542
|
+
return str;
|
|
1543
|
+
}
|
|
1544
|
+
function encodeRandom(len) {
|
|
1545
|
+
const bytes = randomBytes2(len);
|
|
1546
|
+
let str = "";
|
|
1547
|
+
for (let i = 0; i < len; i++) {
|
|
1548
|
+
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
1549
|
+
}
|
|
1550
|
+
return str;
|
|
1551
|
+
}
|
|
1552
|
+
function ulid(seedTime = Date.now()) {
|
|
1553
|
+
return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1502
1556
|
// src/utils/wstack-paths.ts
|
|
1503
1557
|
import { createHash } from "node:crypto";
|
|
1504
1558
|
import * as fs2 from "node:fs";
|
|
@@ -1551,6 +1605,27 @@ function resolveWstackPaths(opts) {
|
|
|
1551
1605
|
homeDir,
|
|
1552
1606
|
configDir: globalRoot,
|
|
1553
1607
|
globalConfig: path2.join(globalRoot, "config.json"),
|
|
1608
|
+
profilesDir: path2.join(globalRoot, "profiles"),
|
|
1609
|
+
profileConfig: (name) => {
|
|
1610
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
1611
|
+
return path2.join(globalRoot, "profiles", safe || "default", "config.json");
|
|
1612
|
+
},
|
|
1613
|
+
profileStatuslineConfig: (name) => {
|
|
1614
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
1615
|
+
return path2.join(globalRoot, "profiles", safe || "default", "statusline.json");
|
|
1616
|
+
},
|
|
1617
|
+
profileModeConfig: (name) => {
|
|
1618
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
1619
|
+
return path2.join(globalRoot, "profiles", safe || "default", "mode.json");
|
|
1620
|
+
},
|
|
1621
|
+
profileProviderStatus: (name) => {
|
|
1622
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
1623
|
+
return path2.join(globalRoot, "profiles", safe || "default", "provider-status.json");
|
|
1624
|
+
},
|
|
1625
|
+
profileUpdateCache: (name) => {
|
|
1626
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
1627
|
+
return path2.join(globalRoot, "profiles", safe || "default", "update-cache.json");
|
|
1628
|
+
},
|
|
1554
1629
|
secretsKey: path2.join(globalRoot, ".key"),
|
|
1555
1630
|
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
1556
1631
|
globalSkills: path2.join(globalRoot, "skills"),
|
|
@@ -1590,6 +1665,7 @@ function resolveWstackPaths(opts) {
|
|
|
1590
1665
|
projectAutophase: path2.join(projectDir, "autophase"),
|
|
1591
1666
|
projectSddBoards: path2.join(projectDir, "sdd-boards"),
|
|
1592
1667
|
syncConfig: path2.join(globalRoot, "sync.json"),
|
|
1668
|
+
configHistoryDir: path2.join(globalRoot, "config-history"),
|
|
1593
1669
|
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
1594
1670
|
};
|
|
1595
1671
|
}
|
|
@@ -2559,18 +2635,20 @@ function isTextBlock(b) {
|
|
|
2559
2635
|
// src/types/provider.ts
|
|
2560
2636
|
var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
|
|
2561
2637
|
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
2638
|
+
var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
|
|
2562
2639
|
function classifyProviderError(status, body, message) {
|
|
2563
2640
|
const type = body?.type;
|
|
2641
|
+
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
2564
2642
|
if (status === 0) return "network";
|
|
2565
2643
|
if (status === 408) return "timeout";
|
|
2566
2644
|
if (status === 599) return "stream_hang";
|
|
2645
|
+
if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
|
|
2567
2646
|
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
2568
2647
|
if (type === "overloaded_error" || status === 529) return "overloaded";
|
|
2569
2648
|
if (status >= 500) return "server";
|
|
2570
2649
|
if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
|
|
2571
2650
|
return "auth";
|
|
2572
2651
|
}
|
|
2573
|
-
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
2574
2652
|
if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
|
|
2575
2653
|
if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
|
|
2576
2654
|
return "context_overflow";
|
|
@@ -2583,6 +2661,7 @@ function isRetryableKind(kind) {
|
|
|
2583
2661
|
}
|
|
2584
2662
|
var RETRYABLE_BY_KIND = {
|
|
2585
2663
|
rate_limit: true,
|
|
2664
|
+
quota_exhausted: false,
|
|
2586
2665
|
overloaded: true,
|
|
2587
2666
|
server: true,
|
|
2588
2667
|
timeout: true,
|
|
@@ -2599,6 +2678,7 @@ function isFallbackWorthy(kind) {
|
|
|
2599
2678
|
}
|
|
2600
2679
|
var FALLBACK_WORTHY_BY_KIND = {
|
|
2601
2680
|
rate_limit: true,
|
|
2681
|
+
quota_exhausted: true,
|
|
2602
2682
|
overloaded: true,
|
|
2603
2683
|
server: true,
|
|
2604
2684
|
timeout: true,
|
|
@@ -2677,6 +2757,7 @@ var KIND_TO_CODE = {
|
|
|
2677
2757
|
network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
2678
2758
|
timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
2679
2759
|
rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
2760
|
+
quota_exhausted: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
2680
2761
|
auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
|
|
2681
2762
|
overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
|
|
2682
2763
|
context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
|
|
@@ -2690,6 +2771,84 @@ function kindToCode(kind) {
|
|
|
2690
2771
|
return KIND_TO_CODE[kind];
|
|
2691
2772
|
}
|
|
2692
2773
|
|
|
2774
|
+
// src/core/model-availability-calendar.ts
|
|
2775
|
+
function logicalCalendarTarget(providerId, model) {
|
|
2776
|
+
if (providerId !== "omniroute") return { providerId, model };
|
|
2777
|
+
const slash = model.indexOf("/");
|
|
2778
|
+
return slash > 0 && slash < model.length - 1 ? { providerId: model.slice(0, slash), model: model.slice(slash + 1) } : { providerId, model };
|
|
2779
|
+
}
|
|
2780
|
+
var WEEKDAYS = {
|
|
2781
|
+
Sun: 0,
|
|
2782
|
+
Mon: 1,
|
|
2783
|
+
Tue: 2,
|
|
2784
|
+
Wed: 3,
|
|
2785
|
+
Thu: 4,
|
|
2786
|
+
Fri: 5,
|
|
2787
|
+
Sat: 6
|
|
2788
|
+
};
|
|
2789
|
+
function minuteOfDay(value) {
|
|
2790
|
+
const match = /^(\d{2}):(\d{2})$/.exec(value);
|
|
2791
|
+
if (!match) return void 0;
|
|
2792
|
+
const hour = Number(match[1]);
|
|
2793
|
+
const minute = Number(match[2]);
|
|
2794
|
+
if (hour > 23 || minute > 59) return void 0;
|
|
2795
|
+
return hour * 60 + minute;
|
|
2796
|
+
}
|
|
2797
|
+
function clockAt(date, timezone) {
|
|
2798
|
+
try {
|
|
2799
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
2800
|
+
timeZone: timezone,
|
|
2801
|
+
weekday: "short",
|
|
2802
|
+
hour: "2-digit",
|
|
2803
|
+
minute: "2-digit",
|
|
2804
|
+
hourCycle: "h23"
|
|
2805
|
+
}).formatToParts(date);
|
|
2806
|
+
const weekday = parts.find((part) => part.type === "weekday")?.value;
|
|
2807
|
+
const hour = Number(parts.find((part) => part.type === "hour")?.value);
|
|
2808
|
+
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
2809
|
+
if (!weekday || WEEKDAYS[weekday] === void 0 || !Number.isFinite(hour + minute))
|
|
2810
|
+
return void 0;
|
|
2811
|
+
return { day: WEEKDAYS[weekday], minute: hour * 60 + minute };
|
|
2812
|
+
} catch {
|
|
2813
|
+
return void 0;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
function targetMatches(rule, providerId, model) {
|
|
2817
|
+
if (rule.provider && rule.provider !== providerId) return false;
|
|
2818
|
+
if (rule.model && rule.model !== model) return false;
|
|
2819
|
+
return Boolean(rule.provider || rule.model);
|
|
2820
|
+
}
|
|
2821
|
+
function timeMatches(rule, day, minute) {
|
|
2822
|
+
const start = minuteOfDay(rule.start);
|
|
2823
|
+
const end = minuteOfDay(rule.end);
|
|
2824
|
+
if (start === void 0 || end === void 0) return false;
|
|
2825
|
+
const days = rule.days?.length ? new Set(rule.days) : void 0;
|
|
2826
|
+
if (start === end) return !days || days.has(day);
|
|
2827
|
+
if (start < end) return (!days || days.has(day)) && minute >= start && minute < end;
|
|
2828
|
+
if (minute >= start) return !days || days.has(day);
|
|
2829
|
+
const previousDay = (day + 6) % 7;
|
|
2830
|
+
return minute < end && (!days || days.has(previousDay));
|
|
2831
|
+
}
|
|
2832
|
+
function evaluateModelCalendar(rules, providerId, model, at = /* @__PURE__ */ new Date()) {
|
|
2833
|
+
({ providerId, model } = logicalCalendarTarget(providerId, model));
|
|
2834
|
+
const allowRules = [];
|
|
2835
|
+
let allowMatched = false;
|
|
2836
|
+
for (const rule of rules ?? []) {
|
|
2837
|
+
if (rule.enabled === false || !targetMatches(rule, providerId, model)) continue;
|
|
2838
|
+
const clock = clockAt(at, rule.timezone);
|
|
2839
|
+
if (!clock || minuteOfDay(rule.start) === void 0 || minuteOfDay(rule.end) === void 0)
|
|
2840
|
+
continue;
|
|
2841
|
+
if (rule.mode === "allow_only") {
|
|
2842
|
+
allowRules.push(rule);
|
|
2843
|
+
if (timeMatches(rule, clock.day, clock.minute)) allowMatched = true;
|
|
2844
|
+
} else if (timeMatches(rule, clock.day, clock.minute)) {
|
|
2845
|
+
return { allowed: false, rule };
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (allowRules.length > 0 && !allowMatched) return { allowed: false, rule: allowRules[0] };
|
|
2849
|
+
return { allowed: true };
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2693
2852
|
// src/core/fallback-model.ts
|
|
2694
2853
|
function parseModelRef(ref) {
|
|
2695
2854
|
const trimmed = ref.trim();
|
|
@@ -3157,9 +3316,9 @@ function normalizeOptions(options) {
|
|
|
3157
3316
|
};
|
|
3158
3317
|
});
|
|
3159
3318
|
}
|
|
3160
|
-
function requiredInstruction(
|
|
3161
|
-
const text = readBundledInstructionText(
|
|
3162
|
-
if (!text) throw new Error(`Council instruction file is unavailable: ${
|
|
3319
|
+
function requiredInstruction(path12) {
|
|
3320
|
+
const text = readBundledInstructionText(path12);
|
|
3321
|
+
if (!text) throw new Error(`Council instruction file is unavailable: ${path12}`);
|
|
3163
3322
|
return text;
|
|
3164
3323
|
}
|
|
3165
3324
|
|
|
@@ -5921,12 +6080,12 @@ function verifyFiles(tokens, files) {
|
|
|
5921
6080
|
const violations = [];
|
|
5922
6081
|
let onPalette = 0;
|
|
5923
6082
|
let offPalette = 0;
|
|
5924
|
-
for (const { path:
|
|
6083
|
+
for (const { path: path12, text } of files) {
|
|
5925
6084
|
const lines = text.split("\n");
|
|
5926
6085
|
lines.forEach((lineText, i) => {
|
|
5927
6086
|
const lineNo = i + 1;
|
|
5928
6087
|
const flag = (snippet, reason) => {
|
|
5929
|
-
violations.push({ file:
|
|
6088
|
+
violations.push({ file: path12, line: lineNo, snippet: snippet.slice(0, 80), reason });
|
|
5930
6089
|
};
|
|
5931
6090
|
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
5932
6091
|
re.lastIndex = 0;
|
|
@@ -6214,16 +6373,20 @@ function buildRecoveryStrategies(opts) {
|
|
|
6214
6373
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
6215
6374
|
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
6216
6375
|
if (ctx?.signal) {
|
|
6217
|
-
await new Promise((
|
|
6376
|
+
await new Promise((resolve6) => {
|
|
6218
6377
|
if (ctx.signal.aborted) {
|
|
6219
|
-
|
|
6378
|
+
resolve6();
|
|
6220
6379
|
return;
|
|
6221
6380
|
}
|
|
6222
|
-
const timer = setTimeout(
|
|
6223
|
-
ctx.signal.addEventListener(
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6381
|
+
const timer = setTimeout(resolve6, delay);
|
|
6382
|
+
ctx.signal.addEventListener(
|
|
6383
|
+
"abort",
|
|
6384
|
+
() => {
|
|
6385
|
+
clearTimeout(timer);
|
|
6386
|
+
resolve6();
|
|
6387
|
+
},
|
|
6388
|
+
{ once: true }
|
|
6389
|
+
);
|
|
6227
6390
|
});
|
|
6228
6391
|
} else {
|
|
6229
6392
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -6314,6 +6477,7 @@ function buildRecoveryStrategies(opts) {
|
|
|
6314
6477
|
var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
|
|
6315
6478
|
var HANDLER_KIND_BY_PROVIDER_KIND = {
|
|
6316
6479
|
rate_limit: "rate_limit",
|
|
6480
|
+
quota_exhausted: "rate_limit",
|
|
6317
6481
|
overloaded: "overloaded",
|
|
6318
6482
|
server: "server",
|
|
6319
6483
|
stream_hang: "server",
|
|
@@ -7923,13 +8087,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
7923
8087
|
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
7924
8088
|
return Promise.resolve("stop");
|
|
7925
8089
|
}
|
|
7926
|
-
return new Promise((
|
|
8090
|
+
return new Promise((resolve6) => {
|
|
7927
8091
|
let resolved = false;
|
|
7928
8092
|
const respond = (d) => {
|
|
7929
8093
|
if (resolved) return;
|
|
7930
8094
|
resolved = true;
|
|
7931
8095
|
clearTimeout(fallback);
|
|
7932
|
-
|
|
8096
|
+
resolve6(d);
|
|
7933
8097
|
};
|
|
7934
8098
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
7935
8099
|
const sessionId = this.currentSessionId();
|
|
@@ -8228,8 +8392,34 @@ function makeAgentSubagentRunner(opts) {
|
|
|
8228
8392
|
const taskStartedAt = Date.now();
|
|
8229
8393
|
const factoryResult = await opts.factory(ctx.config, task);
|
|
8230
8394
|
const { agent, events } = factoryResult;
|
|
8395
|
+
const taskContext = task.context ?? {};
|
|
8396
|
+
const hasExplicitTelemetryTask = typeof taskContext["telemetryTaskId"] === "string";
|
|
8397
|
+
const telemetryTaskId = hasExplicitTelemetryTask ? taskContext["telemetryTaskId"] : task.id;
|
|
8398
|
+
const telemetryRunId = typeof taskContext["telemetryRunId"] === "string" ? taskContext["telemetryRunId"] : void 0;
|
|
8399
|
+
const telemetryBoardId = typeof taskContext["telemetryBoardId"] === "string" ? taskContext["telemetryBoardId"] : void 0;
|
|
8231
8400
|
const agentContext = agent.ctx;
|
|
8232
|
-
if (agentContext?.meta)
|
|
8401
|
+
if (agentContext?.meta) {
|
|
8402
|
+
agentContext.meta["subagentTaskId"] = task.id;
|
|
8403
|
+
const kanbanContext = taskContext["kanban"];
|
|
8404
|
+
if (kanbanContext && typeof kanbanContext === "object") {
|
|
8405
|
+
agentContext.meta["kanban"] = kanbanContext;
|
|
8406
|
+
}
|
|
8407
|
+
if (hasExplicitTelemetryTask) {
|
|
8408
|
+
agentContext.meta["telemetryTaskId"] = telemetryTaskId;
|
|
8409
|
+
if (telemetryRunId) agentContext.meta["telemetryRunId"] = telemetryRunId;
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
if (hasExplicitTelemetryTask) {
|
|
8413
|
+
agentContext?.setCurrentKanbanTask?.(telemetryTaskId, telemetryBoardId);
|
|
8414
|
+
} else {
|
|
8415
|
+
const kanbanContext = taskContext["kanban"];
|
|
8416
|
+
if (kanbanContext && typeof kanbanContext === "object") {
|
|
8417
|
+
const kanban = kanbanContext;
|
|
8418
|
+
const boardId = typeof kanban["boardId"] === "string" ? kanban["boardId"] : void 0;
|
|
8419
|
+
const taskId = typeof kanban["taskId"] === "string" ? kanban["taskId"] : void 0;
|
|
8420
|
+
if (taskId) agentContext?.setCurrentKanbanTask?.(taskId, boardId);
|
|
8421
|
+
}
|
|
8422
|
+
}
|
|
8233
8423
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
8234
8424
|
const aborter = new AbortController();
|
|
8235
8425
|
ctx.budget._events = events;
|
|
@@ -8254,6 +8444,27 @@ function makeAgentSubagentRunner(opts) {
|
|
|
8254
8444
|
const unsub = [];
|
|
8255
8445
|
unsub.push(
|
|
8256
8446
|
events.on("tool.executed", (e) => {
|
|
8447
|
+
if (hasExplicitTelemetryTask) {
|
|
8448
|
+
opts.hostEvents?.emit("subagent.tool_executed", {
|
|
8449
|
+
sessionId: e.sessionId,
|
|
8450
|
+
subagentId: ctx.subagentId,
|
|
8451
|
+
agentSessionId: e.sessionId,
|
|
8452
|
+
agentName: e.agentName ?? ctx.config.name,
|
|
8453
|
+
taskId: telemetryTaskId,
|
|
8454
|
+
runId: telemetryRunId,
|
|
8455
|
+
traceId: e.traceId,
|
|
8456
|
+
id: e.id,
|
|
8457
|
+
name: e.name,
|
|
8458
|
+
durationMs: e.durationMs,
|
|
8459
|
+
ok: e.ok,
|
|
8460
|
+
// Raw tool inputs may contain credentials or sensitive arguments.
|
|
8461
|
+
// Task telemetry crosses from the isolated worker bus to the shared
|
|
8462
|
+
// host bus, so expose only execution metadata at this boundary.
|
|
8463
|
+
outputBytes: e.outputBytes,
|
|
8464
|
+
outputTokens: e.outputTokens,
|
|
8465
|
+
outputLines: e.outputLines
|
|
8466
|
+
});
|
|
8467
|
+
}
|
|
8257
8468
|
try {
|
|
8258
8469
|
ctx.budget.recordToolCall();
|
|
8259
8470
|
} catch (eb) {
|
|
@@ -8265,6 +8476,17 @@ function makeAgentSubagentRunner(opts) {
|
|
|
8265
8476
|
lastToolFailed = null;
|
|
8266
8477
|
}
|
|
8267
8478
|
}),
|
|
8479
|
+
events.on("file.event", (e) => {
|
|
8480
|
+
if (!hasExplicitTelemetryTask) return;
|
|
8481
|
+
if (e.runId === telemetryRunId && e.taskId === telemetryTaskId) return;
|
|
8482
|
+
opts.hostEvents?.emit("file.event", {
|
|
8483
|
+
...e,
|
|
8484
|
+
scope: "task",
|
|
8485
|
+
taskId: telemetryTaskId,
|
|
8486
|
+
boardId: telemetryBoardId,
|
|
8487
|
+
runId: telemetryRunId
|
|
8488
|
+
});
|
|
8489
|
+
}),
|
|
8268
8490
|
events.on("provider.response", (e) => {
|
|
8269
8491
|
try {
|
|
8270
8492
|
ctx.budget.recordUsage(e.usage);
|
|
@@ -9558,6 +9780,40 @@ var DOMAIN_AGENTS = [
|
|
|
9558
9780
|
"information architecture"
|
|
9559
9781
|
]
|
|
9560
9782
|
}
|
|
9783
|
+
},
|
|
9784
|
+
{
|
|
9785
|
+
config: {
|
|
9786
|
+
id: "ios",
|
|
9787
|
+
name: "iOS",
|
|
9788
|
+
role: "ios",
|
|
9789
|
+
tools: [...TOOLS.build, "fetch"],
|
|
9790
|
+
prompt: agentPrompt("ios")
|
|
9791
|
+
},
|
|
9792
|
+
budget: HEAVY_BUDGET,
|
|
9793
|
+
capability: {
|
|
9794
|
+
phase: "domain",
|
|
9795
|
+
summary: "Apple-platform app development in Swift against the latest Xcode/iOS-SDK line: SwiftUI, UIKit, SwiftData, concurrency, accessibility, and App Store submission.",
|
|
9796
|
+
keywords: [
|
|
9797
|
+
"ios",
|
|
9798
|
+
"iphone",
|
|
9799
|
+
"ipad",
|
|
9800
|
+
"ipados",
|
|
9801
|
+
"watchos",
|
|
9802
|
+
"tvos",
|
|
9803
|
+
"visionos",
|
|
9804
|
+
"macos",
|
|
9805
|
+
"swift",
|
|
9806
|
+
"swiftui",
|
|
9807
|
+
"uikit",
|
|
9808
|
+
"xcode",
|
|
9809
|
+
"swiftdata",
|
|
9810
|
+
"app store",
|
|
9811
|
+
"app intents",
|
|
9812
|
+
"swift package manager",
|
|
9813
|
+
"cocoapods",
|
|
9814
|
+
"foundation models"
|
|
9815
|
+
]
|
|
9816
|
+
}
|
|
9561
9817
|
}
|
|
9562
9818
|
];
|
|
9563
9819
|
|
|
@@ -10166,6 +10422,7 @@ function classifySubagentError(err, hints = {}) {
|
|
|
10166
10422
|
function providerErrorToSubagentError(err, message, cause) {
|
|
10167
10423
|
switch (err.kind) {
|
|
10168
10424
|
case "rate_limit":
|
|
10425
|
+
case "quota_exhausted":
|
|
10169
10426
|
return {
|
|
10170
10427
|
kind: "provider_rate_limit",
|
|
10171
10428
|
message,
|
|
@@ -10665,7 +10922,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
10665
10922
|
taskIds.map((id) => {
|
|
10666
10923
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
10667
10924
|
if (cached) return cached;
|
|
10668
|
-
return new Promise((
|
|
10925
|
+
return new Promise((resolve6, reject) => {
|
|
10669
10926
|
const timeout = setTimeout(() => {
|
|
10670
10927
|
this.off("task.completed", handler);
|
|
10671
10928
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -10674,7 +10931,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
10674
10931
|
if (result.taskId === id) {
|
|
10675
10932
|
clearTimeout(timeout);
|
|
10676
10933
|
this.off("task.completed", handler);
|
|
10677
|
-
|
|
10934
|
+
resolve6(result);
|
|
10678
10935
|
}
|
|
10679
10936
|
};
|
|
10680
10937
|
this.on("task.completed", handler);
|
|
@@ -10699,13 +10956,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
10699
10956
|
const done = new Set(completed.map((r) => r.taskId));
|
|
10700
10957
|
return { completed, pending: taskIds.filter((id) => !done.has(id)) };
|
|
10701
10958
|
}
|
|
10702
|
-
return new Promise((
|
|
10959
|
+
return new Promise((resolve6) => {
|
|
10703
10960
|
let timer;
|
|
10704
10961
|
const handler = ({ result }) => {
|
|
10705
10962
|
if (!ids.has(result.taskId)) return;
|
|
10706
10963
|
if (timer) clearTimeout(timer);
|
|
10707
10964
|
this.off("task.completed", handler);
|
|
10708
|
-
|
|
10965
|
+
resolve6({
|
|
10709
10966
|
completed: [result],
|
|
10710
10967
|
pending: taskIds.filter((id) => id !== result.taskId)
|
|
10711
10968
|
});
|
|
@@ -10713,7 +10970,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
10713
10970
|
if (opts?.timeoutMs !== void 0) {
|
|
10714
10971
|
timer = setTimeout(() => {
|
|
10715
10972
|
this.off("task.completed", handler);
|
|
10716
|
-
|
|
10973
|
+
resolve6({ completed: [], pending: [...taskIds], timedOut: true });
|
|
10717
10974
|
}, opts.timeoutMs);
|
|
10718
10975
|
}
|
|
10719
10976
|
this.on("task.completed", handler);
|
|
@@ -10995,12 +11252,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
10995
11252
|
}
|
|
10996
11253
|
return new Promise((resolveDecision) => {
|
|
10997
11254
|
let settled = false;
|
|
10998
|
-
const
|
|
11255
|
+
const resolve6 = (d) => {
|
|
10999
11256
|
if (settled) return;
|
|
11000
11257
|
settled = true;
|
|
11001
11258
|
resolveDecision(d);
|
|
11002
11259
|
};
|
|
11003
|
-
const fallback = setTimeout(() =>
|
|
11260
|
+
const fallback = setTimeout(() => resolve6("stop"), DECISION_TIMEOUT_MS);
|
|
11004
11261
|
const sessionId = this.currentSessionId();
|
|
11005
11262
|
budget._events?.emit("budget.threshold_reached", {
|
|
11006
11263
|
...sessionId ? { sessionId } : {},
|
|
@@ -11018,11 +11275,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
11018
11275
|
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
11019
11276
|
extend: (extra) => {
|
|
11020
11277
|
clearTimeout(fallback);
|
|
11021
|
-
queueMicrotask(() =>
|
|
11278
|
+
queueMicrotask(() => resolve6({ extend: extra }));
|
|
11022
11279
|
},
|
|
11023
11280
|
deny: () => {
|
|
11024
11281
|
clearTimeout(fallback);
|
|
11025
|
-
|
|
11282
|
+
resolve6("stop");
|
|
11026
11283
|
}
|
|
11027
11284
|
});
|
|
11028
11285
|
});
|
|
@@ -11690,6 +11947,7 @@ import { randomInt } from "node:crypto";
|
|
|
11690
11947
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
11691
11948
|
var MAX_ATTEMPTS_BY_KIND = {
|
|
11692
11949
|
rate_limit: 5,
|
|
11950
|
+
quota_exhausted: 0,
|
|
11693
11951
|
stream_hang: 5,
|
|
11694
11952
|
// transient, worth retrying aggressively
|
|
11695
11953
|
overloaded: 3,
|
|
@@ -12428,7 +12686,9 @@ var DefaultSkillLoader = class {
|
|
|
12428
12686
|
const seen = /* @__PURE__ */ new Set();
|
|
12429
12687
|
for (const { dir, source, originTool } of this.dirs) {
|
|
12430
12688
|
try {
|
|
12431
|
-
const entries = await fs5.readdir(dir, { withFileTypes: true })
|
|
12689
|
+
const entries = (await fs5.readdir(dir, { withFileTypes: true })).sort(
|
|
12690
|
+
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
12691
|
+
);
|
|
12432
12692
|
for (const e of entries) {
|
|
12433
12693
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
12434
12694
|
const skillFile = path7.join(dir, e.name, "SKILL.md");
|
|
@@ -13055,7 +13315,7 @@ function readPolicy(ctx) {
|
|
|
13055
13315
|
}
|
|
13056
13316
|
|
|
13057
13317
|
// src/execution/tool-executor.ts
|
|
13058
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
13318
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
13059
13319
|
|
|
13060
13320
|
// src/observability/process-telemetry.ts
|
|
13061
13321
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -13074,7 +13334,166 @@ function runWithNetworkTelemetry(context, run) {
|
|
|
13074
13334
|
// src/execution/tool-executor.ts
|
|
13075
13335
|
import { isDeepStrictEqual } from "node:util";
|
|
13076
13336
|
import * as fs8 from "node:fs/promises";
|
|
13337
|
+
import * as path11 from "node:path";
|
|
13338
|
+
|
|
13339
|
+
// src/security/kanban-boundary.ts
|
|
13340
|
+
import { realpath } from "node:fs/promises";
|
|
13077
13341
|
import * as path10 from "node:path";
|
|
13342
|
+
import {
|
|
13343
|
+
evaluateKanbanBoundaryOpaque,
|
|
13344
|
+
evaluateKanbanBoundaryPath,
|
|
13345
|
+
readBoard,
|
|
13346
|
+
resolveKanbanBoundaryLayers
|
|
13347
|
+
} from "@wrongstack/kanban";
|
|
13348
|
+
var PATH_KEYS = /* @__PURE__ */ new Set([
|
|
13349
|
+
"path",
|
|
13350
|
+
"paths",
|
|
13351
|
+
"file",
|
|
13352
|
+
"files",
|
|
13353
|
+
"directory",
|
|
13354
|
+
"dir",
|
|
13355
|
+
"cwd",
|
|
13356
|
+
"root",
|
|
13357
|
+
"baseDir",
|
|
13358
|
+
"out",
|
|
13359
|
+
"outputPath",
|
|
13360
|
+
"sourcePath",
|
|
13361
|
+
"targetPath",
|
|
13362
|
+
"destinationPath",
|
|
13363
|
+
"fromPath",
|
|
13364
|
+
"toPath",
|
|
13365
|
+
"worktreePath"
|
|
13366
|
+
]);
|
|
13367
|
+
var TOOL_PATH_KEYS = {
|
|
13368
|
+
// `target` is an identifier in tools such as plan/task/document, but a
|
|
13369
|
+
// filesystem target in language_info. Keep ambiguous names tool-specific.
|
|
13370
|
+
language_info: ["target"]
|
|
13371
|
+
};
|
|
13372
|
+
async function evaluateToolKanbanBoundary(tool, input, ctx) {
|
|
13373
|
+
const identity = resolveKanbanIdentity(ctx);
|
|
13374
|
+
if (!identity.boardId) return { decision: "allow" };
|
|
13375
|
+
const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
|
|
13376
|
+
if (!board) return { decision: "allow" };
|
|
13377
|
+
const task = identity.taskId ? board.tasks.find((candidate) => candidate.id === identity.taskId) : void 0;
|
|
13378
|
+
const layers = resolveKanbanBoundaryLayers(board, task);
|
|
13379
|
+
if (layers.length === 0) return { decision: "allow" };
|
|
13380
|
+
const caps = tool.capabilities ?? [];
|
|
13381
|
+
const access2 = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
|
|
13382
|
+
const shellLike = caps.some((capability) => capability.startsWith("shell."));
|
|
13383
|
+
const candidates = access2 ? await extractCandidatePaths(tool.name, input, ctx) : [];
|
|
13384
|
+
let evaluation;
|
|
13385
|
+
if (shellLike || access2 && candidates.length === 0) {
|
|
13386
|
+
evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
|
|
13387
|
+
} else if (access2) {
|
|
13388
|
+
const results = candidates.map(
|
|
13389
|
+
(candidate) => evaluateKanbanBoundaryPath(layers, candidate, access2)
|
|
13390
|
+
);
|
|
13391
|
+
evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
|
|
13392
|
+
} else {
|
|
13393
|
+
evaluation = { decision: "allow" };
|
|
13394
|
+
}
|
|
13395
|
+
return {
|
|
13396
|
+
...evaluation,
|
|
13397
|
+
boardId: board.id,
|
|
13398
|
+
...task ? { taskId: task.id } : {}
|
|
13399
|
+
};
|
|
13400
|
+
}
|
|
13401
|
+
function resolveKanbanIdentity(ctx) {
|
|
13402
|
+
const metaKanban = ctx.meta["kanban"];
|
|
13403
|
+
const record = metaKanban && typeof metaKanban === "object" ? metaKanban : void 0;
|
|
13404
|
+
const boardId = ctx.currentKanbanBoardId ?? (typeof record?.["boardId"] === "string" ? record["boardId"] : void 0);
|
|
13405
|
+
const taskId = ctx.currentKanbanTaskId ?? (typeof record?.["taskId"] === "string" ? record["taskId"] : void 0);
|
|
13406
|
+
const policyRoot = typeof record?.["projectRoot"] === "string" ? record["projectRoot"] : void 0;
|
|
13407
|
+
return {
|
|
13408
|
+
...boardId ? { boardId } : {},
|
|
13409
|
+
...taskId ? { taskId } : {},
|
|
13410
|
+
...policyRoot ? { policyRoot } : {}
|
|
13411
|
+
};
|
|
13412
|
+
}
|
|
13413
|
+
async function extractCandidatePaths(toolName, input, ctx) {
|
|
13414
|
+
if (toolName === "patch" && typeof input["patch"] === "string") {
|
|
13415
|
+
const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
|
|
13416
|
+
const directory = path10.isAbsolute(directoryInput) ? directoryInput : path10.resolve(ctx.workingDir, directoryInput);
|
|
13417
|
+
const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
|
|
13418
|
+
const targets = extractPatchTargets(input["patch"], strip).map(
|
|
13419
|
+
(target) => relativeToProject(path10.resolve(directory, target), ctx.projectRoot)
|
|
13420
|
+
);
|
|
13421
|
+
return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
|
|
13422
|
+
}
|
|
13423
|
+
const values = [];
|
|
13424
|
+
const pathKeys = new Set(PATH_KEYS);
|
|
13425
|
+
for (const key of TOOL_PATH_KEYS[toolName] ?? []) pathKeys.add(key);
|
|
13426
|
+
collectPathValues(input, values, pathKeys);
|
|
13427
|
+
if (toolName === "scaffold" && typeof input["name"] === "string") {
|
|
13428
|
+
const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
|
|
13429
|
+
values.push(path10.join(cwd, input["name"]));
|
|
13430
|
+
}
|
|
13431
|
+
const candidates = [
|
|
13432
|
+
...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
|
|
13433
|
+
];
|
|
13434
|
+
return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
|
|
13435
|
+
}
|
|
13436
|
+
async function canonicalizeCandidatePath(candidate, ctx) {
|
|
13437
|
+
if (path10.isAbsolute(candidate)) return candidate;
|
|
13438
|
+
const absolute = path10.resolve(ctx.projectRoot, candidate);
|
|
13439
|
+
const canonicalRoot = await realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
13440
|
+
let probe = absolute;
|
|
13441
|
+
const missingSegments = [];
|
|
13442
|
+
while (true) {
|
|
13443
|
+
try {
|
|
13444
|
+
const canonical = path10.join(await realpath(probe), ...missingSegments);
|
|
13445
|
+
return relativeToProject(canonical, canonicalRoot);
|
|
13446
|
+
} catch (cause) {
|
|
13447
|
+
const code = cause.code;
|
|
13448
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
|
|
13449
|
+
const parent = path10.dirname(probe);
|
|
13450
|
+
if (parent === probe) return absolute;
|
|
13451
|
+
missingSegments.unshift(path10.basename(probe));
|
|
13452
|
+
probe = parent;
|
|
13453
|
+
}
|
|
13454
|
+
}
|
|
13455
|
+
}
|
|
13456
|
+
function collectPathValues(value, output, pathKeys, key) {
|
|
13457
|
+
if (typeof value === "string") {
|
|
13458
|
+
if (key && pathKeys.has(key)) output.push(value);
|
|
13459
|
+
return;
|
|
13460
|
+
}
|
|
13461
|
+
if (Array.isArray(value)) {
|
|
13462
|
+
for (const item of value) collectPathValues(item, output, pathKeys, key);
|
|
13463
|
+
return;
|
|
13464
|
+
}
|
|
13465
|
+
if (!value || typeof value !== "object") return;
|
|
13466
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
13467
|
+
collectPathValues(child, output, pathKeys, childKey);
|
|
13468
|
+
}
|
|
13469
|
+
}
|
|
13470
|
+
function splitPathList(value) {
|
|
13471
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
13472
|
+
}
|
|
13473
|
+
function resolveInputPath(value, ctx) {
|
|
13474
|
+
const absolute = path10.isAbsolute(value) ? value : path10.resolve(ctx.workingDir, value);
|
|
13475
|
+
return relativeToProject(absolute, ctx.projectRoot);
|
|
13476
|
+
}
|
|
13477
|
+
function relativeToProject(absolute, projectRoot) {
|
|
13478
|
+
const relative2 = path10.relative(projectRoot, absolute).replace(/\\/g, "/");
|
|
13479
|
+
return relative2.startsWith("../") || path10.isAbsolute(relative2) ? absolute : relative2 || ".";
|
|
13480
|
+
}
|
|
13481
|
+
function extractPatchTargets(patchText, strip) {
|
|
13482
|
+
const targets = [];
|
|
13483
|
+
for (const match of patchText.matchAll(/^\+\+\+\s+([^\t\r\n]+)/gm)) {
|
|
13484
|
+
const raw = match[1]?.trim();
|
|
13485
|
+
if (!raw || raw === "/dev/null") continue;
|
|
13486
|
+
const parts = raw.replace(/\\/g, "/").split("/").filter((part) => part && part !== ".");
|
|
13487
|
+
if (parts.length > strip) targets.push(parts.slice(strip).join("/"));
|
|
13488
|
+
}
|
|
13489
|
+
return targets;
|
|
13490
|
+
}
|
|
13491
|
+
function stringValue(value) {
|
|
13492
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
13493
|
+
}
|
|
13494
|
+
function numericValue(value) {
|
|
13495
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
13496
|
+
}
|
|
13078
13497
|
|
|
13079
13498
|
// src/types/tool-executor.ts
|
|
13080
13499
|
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
@@ -13295,35 +13714,68 @@ ${errorDetails}`,
|
|
|
13295
13714
|
return { result, tool, durationMs: Date.now() - start };
|
|
13296
13715
|
}
|
|
13297
13716
|
}
|
|
13717
|
+
const boundary = await evaluateToolKanbanBoundary(tool, use.input, ctx);
|
|
13718
|
+
if (boundary.decision === "block") {
|
|
13719
|
+
const result = {
|
|
13720
|
+
type: "tool_result",
|
|
13721
|
+
tool_use_id: use.id,
|
|
13722
|
+
content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
|
|
13723
|
+
is_error: true
|
|
13724
|
+
};
|
|
13725
|
+
budget = this.budgetForString(result.content, budget);
|
|
13726
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
13727
|
+
}
|
|
13298
13728
|
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
13299
13729
|
let effectivePermission = decision.permission;
|
|
13300
13730
|
const policy = this.opts.permissionPolicy;
|
|
13301
13731
|
const yolo = policy.getYolo?.() === true;
|
|
13302
13732
|
const authoritativeAuto = decision.source === "yolo";
|
|
13303
|
-
|
|
13733
|
+
const capabilityDowngraded = toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto;
|
|
13734
|
+
if (capabilityDowngraded) {
|
|
13735
|
+
effectivePermission = "confirm";
|
|
13736
|
+
}
|
|
13737
|
+
if (boundary.decision === "confirm" && effectivePermission !== "deny") {
|
|
13304
13738
|
effectivePermission = "confirm";
|
|
13305
13739
|
}
|
|
13740
|
+
this.opts.events?.emit("permission.evaluated", {
|
|
13741
|
+
sessionId: ctx.session.id,
|
|
13742
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
13743
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
13744
|
+
name: tool.name,
|
|
13745
|
+
id: use.id,
|
|
13746
|
+
inputHash: hashPermissionInput(use.input, this.opts.secretScrubber),
|
|
13747
|
+
policyDecision: decision.permission,
|
|
13748
|
+
effectiveDecision: effectivePermission,
|
|
13749
|
+
decisionSource: decision.source,
|
|
13750
|
+
...decision.reason ? { reason: decision.reason } : {},
|
|
13751
|
+
...decision.riskTier ?? tool.riskTier ? { riskTier: decision.riskTier ?? tool.riskTier } : {},
|
|
13752
|
+
yoloEnabled: yolo,
|
|
13753
|
+
boundaryDecision: boundary.decision,
|
|
13754
|
+
...boundary.reason ? { boundaryReason: boundary.reason } : {},
|
|
13755
|
+
capabilityDowngraded
|
|
13756
|
+
});
|
|
13306
13757
|
if (effectivePermission === "deny") {
|
|
13307
13758
|
const result = this.deniedResult(use, decision.reason);
|
|
13308
13759
|
budget = this.budgetForString(result.content, budget);
|
|
13309
13760
|
return { result, tool, durationMs: Date.now() - start };
|
|
13310
13761
|
}
|
|
13311
13762
|
if (effectivePermission === "confirm") {
|
|
13763
|
+
const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
13312
13764
|
if (this.opts.confirmAwaiter) {
|
|
13313
13765
|
const awaiter = this.opts.confirmAwaiter;
|
|
13314
13766
|
const choice = await new Promise(
|
|
13315
|
-
(
|
|
13767
|
+
(resolve6, reject) => {
|
|
13316
13768
|
const signal = ctx.signal;
|
|
13317
|
-
const onAbort = () =>
|
|
13769
|
+
const onAbort = () => resolve6("abort");
|
|
13318
13770
|
if (signal.aborted) {
|
|
13319
|
-
|
|
13771
|
+
resolve6("abort");
|
|
13320
13772
|
return;
|
|
13321
13773
|
}
|
|
13322
13774
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
13323
|
-
awaiter(tool, use.input, use.id,
|
|
13775
|
+
awaiter(tool, use.input, use.id, suggestedPattern).then(
|
|
13324
13776
|
(c) => {
|
|
13325
13777
|
signal.removeEventListener("abort", onAbort);
|
|
13326
|
-
|
|
13778
|
+
resolve6(c);
|
|
13327
13779
|
},
|
|
13328
13780
|
(e) => {
|
|
13329
13781
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -13343,7 +13795,6 @@ ${errorDetails}`,
|
|
|
13343
13795
|
return { result, tool, durationMs: Date.now() - start };
|
|
13344
13796
|
}
|
|
13345
13797
|
} else {
|
|
13346
|
-
const suggestedPattern = subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
13347
13798
|
const pending = {
|
|
13348
13799
|
type: "tool_confirm_pending",
|
|
13349
13800
|
toolUseId: use.id,
|
|
@@ -13351,7 +13802,8 @@ ${errorDetails}`,
|
|
|
13351
13802
|
input: use.input,
|
|
13352
13803
|
suggestedPattern,
|
|
13353
13804
|
decisionSource: decision.source,
|
|
13354
|
-
riskTier: decision.riskTier ?? tool.riskTier
|
|
13805
|
+
riskTier: decision.riskTier ?? tool.riskTier,
|
|
13806
|
+
...boundary.decision === "confirm" && boundary.reason ? { boundaryReason: boundary.reason } : {}
|
|
13355
13807
|
};
|
|
13356
13808
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
13357
13809
|
}
|
|
@@ -13365,6 +13817,17 @@ ${errorDetails}`,
|
|
|
13365
13817
|
"tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
|
|
13366
13818
|
});
|
|
13367
13819
|
try {
|
|
13820
|
+
const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
|
|
13821
|
+
const caps = tool.capabilities ?? [];
|
|
13822
|
+
const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
|
|
13823
|
+
const absPath = hasFileCapability && typeof inputPath === "string" ? path11.isAbsolute(inputPath) ? inputPath : path11.resolve(ctx.projectRoot, inputPath) : void 0;
|
|
13824
|
+
let writeTargetExisted;
|
|
13825
|
+
if (tool.name === "write" && caps.includes("fs.write") && absPath) {
|
|
13826
|
+
writeTargetExisted = await fs8.stat(absPath).then(
|
|
13827
|
+
(stat4) => stat4.isFile(),
|
|
13828
|
+
(error) => error.code === "ENOENT" ? false : void 0
|
|
13829
|
+
);
|
|
13830
|
+
}
|
|
13368
13831
|
const producedText = await this.produceToolOutput(tool, use, ctx, budget);
|
|
13369
13832
|
let { block: result, bytes } = this.settleToolOutput(tool, use, producedText, budget);
|
|
13370
13833
|
budget -= bytes;
|
|
@@ -13393,6 +13856,35 @@ ${post.additionalContext}`;
|
|
|
13393
13856
|
span?.setAttribute("tool.is_error", !!result.is_error);
|
|
13394
13857
|
span?.setAttribute("tool.output_bytes", outputChars);
|
|
13395
13858
|
this.logToolSuccess(ctx, use, tool.name, Date.now() - start, outputChars);
|
|
13859
|
+
if (!result.is_error && typeof inputPath === "string" && absPath) {
|
|
13860
|
+
const operation = tool.name === "read" ? "read" : caps.includes("fs.write") && tool.name === "write" ? writeTargetExisted === false ? "create" : "update" : caps.includes("fs.write") ? "update" : "read";
|
|
13861
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
13862
|
+
ctx.recordFileEvent?.({
|
|
13863
|
+
operation,
|
|
13864
|
+
filePath: inputPath,
|
|
13865
|
+
absPath,
|
|
13866
|
+
toolName: tool.name,
|
|
13867
|
+
toolUseId: use.id,
|
|
13868
|
+
durationMs: Date.now() - start
|
|
13869
|
+
});
|
|
13870
|
+
this.opts.events?.emit("file.event", {
|
|
13871
|
+
operation,
|
|
13872
|
+
filePath: inputPath,
|
|
13873
|
+
absPath,
|
|
13874
|
+
sessionId: ctx.session.id,
|
|
13875
|
+
agentId: ctx.agentId,
|
|
13876
|
+
agentName: ctx.agentName,
|
|
13877
|
+
provider: typeof ctx.provider === "object" ? ctx.provider.id : String(ctx.provider),
|
|
13878
|
+
model: ctx.model,
|
|
13879
|
+
toolName: tool.name,
|
|
13880
|
+
toolUseId: use.id,
|
|
13881
|
+
scope: ctx.currentKanbanTaskId ? "task" : "session",
|
|
13882
|
+
taskId: ctx.currentKanbanTaskId,
|
|
13883
|
+
boardId: ctx.currentKanbanBoardId,
|
|
13884
|
+
timestamp: ts,
|
|
13885
|
+
durationMs: Date.now() - start
|
|
13886
|
+
});
|
|
13887
|
+
}
|
|
13396
13888
|
return { result, tool, durationMs: Date.now() - start };
|
|
13397
13889
|
} catch (err) {
|
|
13398
13890
|
if (isWrongStackError(err)) {
|
|
@@ -13621,7 +14113,7 @@ ${post.additionalContext}`;
|
|
|
13621
14113
|
toolPromise.catch(() => {
|
|
13622
14114
|
});
|
|
13623
14115
|
try {
|
|
13624
|
-
output = await new Promise((
|
|
14116
|
+
output = await new Promise((resolve6, reject) => {
|
|
13625
14117
|
const onAbort = () => {
|
|
13626
14118
|
setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
|
|
13627
14119
|
};
|
|
@@ -13629,7 +14121,7 @@ ${post.additionalContext}`;
|
|
|
13629
14121
|
toolPromise.then(
|
|
13630
14122
|
(v) => {
|
|
13631
14123
|
combined.removeEventListener("abort", onAbort);
|
|
13632
|
-
|
|
14124
|
+
resolve6(v);
|
|
13633
14125
|
},
|
|
13634
14126
|
(e) => {
|
|
13635
14127
|
combined.removeEventListener("abort", onAbort);
|
|
@@ -13920,11 +14412,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
13920
14412
|
return content;
|
|
13921
14413
|
}
|
|
13922
14414
|
try {
|
|
13923
|
-
const dir =
|
|
14415
|
+
const dir = path11.join(wstackGlobalRoot(), "tool-output");
|
|
13924
14416
|
await fs8.mkdir(dir, { recursive: true });
|
|
13925
14417
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
13926
14418
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13927
|
-
const filePath =
|
|
14419
|
+
const filePath = path11.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
|
|
13928
14420
|
await fs8.writeFile(filePath, content, "utf8");
|
|
13929
14421
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
13930
14422
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
@@ -13943,6 +14435,15 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
|
|
|
13943
14435
|
return content;
|
|
13944
14436
|
}
|
|
13945
14437
|
}
|
|
14438
|
+
function hashPermissionInput(input, scrubber) {
|
|
14439
|
+
let serialized;
|
|
14440
|
+
try {
|
|
14441
|
+
serialized = JSON.stringify(input) ?? "";
|
|
14442
|
+
} catch {
|
|
14443
|
+
serialized = String(input);
|
|
14444
|
+
}
|
|
14445
|
+
return createHash2("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
|
|
14446
|
+
}
|
|
13946
14447
|
function sliceUtf8Prefix(text, maxBytes) {
|
|
13947
14448
|
if (maxBytes <= 0) return "";
|
|
13948
14449
|
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
@@ -14024,14 +14525,20 @@ var OneShotOrchestrator = class {
|
|
|
14024
14525
|
let servingProviderId = provider.id;
|
|
14025
14526
|
let servingModel = target.model;
|
|
14026
14527
|
let fromFallback = false;
|
|
14027
|
-
let lastError
|
|
14528
|
+
let lastError;
|
|
14028
14529
|
let fallbackEligible = false;
|
|
14029
|
-
if (tracker && !tracker.isAvailable(target.providerId, target.model)) {
|
|
14530
|
+
if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
|
|
14030
14531
|
this.opts.logger?.debug(
|
|
14031
14532
|
`one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
|
|
14032
14533
|
);
|
|
14033
14534
|
} else {
|
|
14034
|
-
const primaryAttempt = await this.tryCall(
|
|
14535
|
+
const primaryAttempt = await this.tryCall(
|
|
14536
|
+
provider,
|
|
14537
|
+
request,
|
|
14538
|
+
signal,
|
|
14539
|
+
target.providerId,
|
|
14540
|
+
target.model
|
|
14541
|
+
);
|
|
14035
14542
|
const result = primaryAttempt.response;
|
|
14036
14543
|
lastError = primaryAttempt.error;
|
|
14037
14544
|
fallbackEligible = primaryAttempt.fallbackEligible;
|
|
@@ -14042,17 +14549,13 @@ var OneShotOrchestrator = class {
|
|
|
14042
14549
|
return this.buildResult(result, servingProviderId, servingModel, false, startedAt);
|
|
14043
14550
|
}
|
|
14044
14551
|
if (!fallbackEligible || chain.length === 0) {
|
|
14045
|
-
return this.buildErrorResult(
|
|
14046
|
-
lastError,
|
|
14047
|
-
target.providerId,
|
|
14048
|
-
target.model,
|
|
14049
|
-
false,
|
|
14050
|
-
startedAt
|
|
14051
|
-
);
|
|
14552
|
+
return this.buildErrorResult(lastError, target.providerId, target.model, false, startedAt);
|
|
14052
14553
|
}
|
|
14053
14554
|
}
|
|
14054
14555
|
const usableChain = tracker ? chain.filter((e) => tracker.isAvailable(e.providerId, e.model)) : chain;
|
|
14055
14556
|
for (const entry of usableChain) {
|
|
14557
|
+
if (!evaluateModelCalendar(config.modelAvailabilitySchedule, entry.providerId, entry.model).allowed)
|
|
14558
|
+
continue;
|
|
14056
14559
|
if (entry.providerId === provider.id && entry.model === target.model) continue;
|
|
14057
14560
|
let fbProvider;
|
|
14058
14561
|
try {
|
|
@@ -14079,7 +14582,13 @@ var OneShotOrchestrator = class {
|
|
|
14079
14582
|
fallbackEligible = attempt.fallbackEligible;
|
|
14080
14583
|
if (!fallbackEligible) break;
|
|
14081
14584
|
}
|
|
14082
|
-
return this.buildErrorResult(
|
|
14585
|
+
return this.buildErrorResult(
|
|
14586
|
+
lastError,
|
|
14587
|
+
servingProviderId,
|
|
14588
|
+
servingModel,
|
|
14589
|
+
fromFallback,
|
|
14590
|
+
startedAt
|
|
14591
|
+
);
|
|
14083
14592
|
}
|
|
14084
14593
|
// ── Private helpers ─────────────────────────────────────────────
|
|
14085
14594
|
/**
|