@rallycry/conveyor-agent 11.0.23 → 11.0.24
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-S73ZNWBJ.js → boot-65PTSADA.js} +2 -2
- package/dist/{chunk-EIHZRSYI.js → chunk-EITUTXXJ.js} +1 -1
- package/dist/{chunk-XJSW42TM.js → chunk-HW6SHIL7.js} +331 -36
- package/dist/{chunk-LBB7DXOC.js → chunk-IWWBG5FI.js} +1 -1
- package/dist/{chunk-W5INK3NE.js → chunk-WCJ6HRVU.js} +3 -0
- package/dist/{chunk-I7QYTA2V.js → chunk-YNI2HFZE.js} +25 -1
- package/dist/cli.js +7 -7
- package/dist/index.d.ts +22 -2
- package/dist/index.js +3 -3
- package/dist/{serve-boot-ODH57W6I.js → serve-boot-THPACDVI.js} +2 -2
- package/dist/{server-L5CQ5EJB.js → server-E6IYHSMN.js} +2 -2
- package/package.json +1 -1
- package/skills/conveyor-plan/SKILL.md +12 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startWorkbenchServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EITUTXXJ.js";
|
|
4
4
|
import {
|
|
5
5
|
TOMBSTONE_MESSAGE,
|
|
6
6
|
isLegacyEntrypointLaunch
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
refreshSkillsAfterCheckout,
|
|
13
13
|
runPreReadyBinds
|
|
14
14
|
} from "./chunk-37J5MMQT.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-WCJ6HRVU.js";
|
|
16
16
|
import {
|
|
17
17
|
GitPrepJob,
|
|
18
18
|
defaultGit,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
spawnOptionsFingerprint,
|
|
30
30
|
transcriptSize,
|
|
31
31
|
turnOptionsFrom
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-WCJ6HRVU.js";
|
|
33
33
|
import {
|
|
34
34
|
AGENT_STATUS_REASON_USER_QUESTION,
|
|
35
35
|
AgentConnection,
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
statWorkspacePath,
|
|
92
92
|
updateRemoteToken,
|
|
93
93
|
verifyGitCredential
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-YNI2HFZE.js";
|
|
95
95
|
import {
|
|
96
96
|
registerBootMilestoneSocketFallback,
|
|
97
97
|
reportBootMilestone
|
|
@@ -831,9 +831,11 @@ function mapUsage(message) {
|
|
|
831
831
|
if (!isRecord(usage)) return void 0;
|
|
832
832
|
const result = {};
|
|
833
833
|
const input = numberField(usage, "input_tokens");
|
|
834
|
+
const output = numberField(usage, "output_tokens");
|
|
834
835
|
const cacheRead = numberField(usage, "cache_read_input_tokens");
|
|
835
836
|
const cacheCreation = numberField(usage, "cache_creation_input_tokens");
|
|
836
837
|
if (input !== void 0) result.input_tokens = input;
|
|
838
|
+
if (output !== void 0) result.output_tokens = output;
|
|
837
839
|
if (cacheRead !== void 0) result.cache_read_input_tokens = cacheRead;
|
|
838
840
|
if (cacheCreation !== void 0) result.cache_creation_input_tokens = cacheCreation;
|
|
839
841
|
return result;
|
|
@@ -1807,8 +1809,13 @@ async function writeHookSettings(dir, opts = {}) {
|
|
|
1807
1809
|
// `--dangerously-skip-permissions`.
|
|
1808
1810
|
// NOTE: a bare "*" rule is INVALID (the CLI rejects it and parks the TUI on
|
|
1809
1811
|
// a Settings Warning dialog at boot) — hence the explicit ALLOW_RULES list.
|
|
1812
|
+
// `deny` carries the tools no pod agent may use (Artifact publishes to an
|
|
1813
|
+
// off-platform claude.ai URL; deliverables belong on the card). Bare tool
|
|
1814
|
+
// names are the valid rule form. The key is omitted when empty — see
|
|
1815
|
+
// HookSettingsOptions.disallowedTools for why that matters.
|
|
1810
1816
|
permissions: {
|
|
1811
|
-
allow: ALLOW_RULES
|
|
1817
|
+
allow: ALLOW_RULES,
|
|
1818
|
+
...opts.disallowedTools && opts.disallowedTools.length > 0 ? { deny: opts.disallowedTools } : {}
|
|
1812
1819
|
},
|
|
1813
1820
|
hooks: {
|
|
1814
1821
|
PreToolUse: buildPreToolUseHooks(helperPath, opts.gateAllTools === true),
|
|
@@ -2606,6 +2613,7 @@ var ClaudeTuiAdapter = class {
|
|
|
2606
2613
|
permissionMode: options.permissionMode,
|
|
2607
2614
|
settingsPath: input.settingsPath ?? "",
|
|
2608
2615
|
...options.appendSystemPrompt ? { appendSystemPrompt: options.appendSystemPrompt } : {},
|
|
2616
|
+
...options.disallowedTools?.length ? { disallowedTools: options.disallowedTools } : {},
|
|
2609
2617
|
...input.mcpConfigPath ? { mcpConfigPath: input.mcpConfigPath, strictMcpConfig: true } : {}
|
|
2610
2618
|
});
|
|
2611
2619
|
return {
|
|
@@ -3155,7 +3163,8 @@ var PtySession = class {
|
|
|
3155
3163
|
);
|
|
3156
3164
|
await this.socket.listen();
|
|
3157
3165
|
const { settingsPath } = await writeHookSettings(this.tempDir, {
|
|
3158
|
-
gateAllTools: this.options.permissionMode === "plan"
|
|
3166
|
+
gateAllTools: this.options.permissionMode === "plan",
|
|
3167
|
+
...this.options.disallowedTools?.length ? { disallowedTools: this.options.disallowedTools } : {}
|
|
3159
3168
|
});
|
|
3160
3169
|
await this.setupToolServers();
|
|
3161
3170
|
const transcriptPath = sessionTranscriptPath(this.options.cwd, sessionId);
|
|
@@ -5641,7 +5650,8 @@ function buildDiscoveryPrompt(context, runnerMode) {
|
|
|
5641
5650
|
] : [],
|
|
5642
5651
|
``,
|
|
5643
5652
|
`### Contracts this environment enforces`,
|
|
5644
|
-
`- You are READ-ONLY. File writes are denied outside \`.claude/plans/\`, and so are the commands that would start the work: git commit/push/merge/rebase/checkout, git apply, gh pr create, package publish. Everything else runs without an approval prompt \u2014 the pod is a sandbox, so investigate freely (build, test, query the dev DB, read logs).`,
|
|
5653
|
+
`- You are READ-ONLY. File writes are denied outside \`.claude/plans/\` and \`/tmp/\`, and so are the commands that would start the work: git commit/push/merge/rebase/checkout, git apply, gh pr create, package publish. Everything else runs without an approval prompt \u2014 the pod is a sandbox, so investigate freely (build, test, query the dev DB, read logs).`,
|
|
5654
|
+
`- Research deliverables the team should keep (a diagram, an HTML report, a screenshot, a data export) go on the card with \`mcp__conveyor__upload_attachment\`: write the file under \`/tmp/\` first, and pass \`title\` as the caption so it shows in the thread. Never publish one as a Claude artifact.`,
|
|
5645
5655
|
`- A plan file on disk is NOT the plan. Only \`update_task\` saves it to the card.`,
|
|
5646
5656
|
`- **ExitPlanMode is the LAST step and it validates.** It fails until the card has a plan, story points, risk, and a title \u2014 the plan comes from \`update_task\`, and story points, risk and title from \`update_task_properties\`. Calling it early wastes a turn on a denial.`,
|
|
5647
5657
|
`- ExitPlanMode does NOT start building. It parks this session for human review; the team decides when to build.`,
|
|
@@ -6340,7 +6350,7 @@ Working rules:`,
|
|
|
6340
6350
|
`- When a build/lint/test run fails, capture its output to a file once and grep the file \u2014 never re-run the suite just to re-filter the same output.`,
|
|
6341
6351
|
`- App servers (including development and production servers) keep running after startup. Start them with the harness background/session facility, then run a separate bounded HTTP readiness check and continue verification. Never wait for server exit or a completion notification to mean the app is ready. If readiness times out, inspect build output and process/resource state before restarting.`,
|
|
6342
6352
|
runtimeTui === "codex" ? `- Waiting on long-running commands: retain and resume the command session until it returns an exit result. Finish each required gate before ending the task. Never pretend a completion notification will resume you, and never start another gate while the current one is running.` : `- Waiting on long-running commands: if a gate finishes in under ~2 minutes, run it in the foreground with a timeout. For a longer one, launch it with run_in_background and STOP; a completion notification arrives when it finishes, and tracked background work holds the workspace awake for up to 45 minutes. Inspect any gate approaching that cap. For the final pre-PR gate a bounded foreground run (\`timeout 590 <gate>\` with Bash \`timeout: 600000\`) is still preferred as defense in depth. A pod restart loses both foreground and background processes. Never busy-wait with sleep/pgrep/tail loops, and never re-run the suite to escape a wait that looks stalled.`,
|
|
6343
|
-
runtimeTui === "codex" ? `- Do not end your turn to wait on a required gate. Resume its command session, inspect its exit result, and then continue the checklist.` : `- For a finite background gate, end your turn and its completion notification re-invokes you within the bounded liveness window. Never emit filler commands (\`echo waiting\`, \`true\`, \`sleep N; echo done\`) to "stay alive" \u2014 they are detected and blocked. The proven long-wait shape: start the job with run_in_background, then end the turn.${agentMode === "review" ? "" : " For CI on a pushed commit the shape is `wait_for_checks`, then end the turn (the pod may sleep; the GitHub result wakes it)."} Arm a ScheduleWakeup (delaySeconds 900-1500, prompt restating your next steps) only when nothing will notify you \u2014 a deploy, a remote queue \u2014 never for CI, and never as insurance against a background job's own notification, which does fire.`,
|
|
6353
|
+
runtimeTui === "codex" ? `- Do not end your turn to wait on a required gate. Resume its command session, inspect its exit result, and then continue the checklist.` : `- For a finite background gate, end your turn and its completion notification re-invokes you within the bounded liveness window. Never emit filler commands (\`echo waiting\`, \`true\`, \`sleep N; echo done\`) to "stay alive" \u2014 they are detected and blocked. The proven long-wait shape: start the job with run_in_background, then end the turn.${agentMode === "review" ? "" : " For CI on a pushed commit the shape is `wait_for_checks`, then end the turn (the pod may sleep; the GitHub result wakes it)."} Arm a ScheduleWakeup (delaySeconds 900-1500, prompt restating your next steps) only when nothing will notify you \u2014 a deploy, a remote queue \u2014 never for CI, and never as insurance against a background job's own notification, which does fire. One armed wakeup keeps this workspace awake until it fires (at most one hour), so arm exactly one and end the turn \u2014 a second one "as insurance" buys nothing.`,
|
|
6344
6354
|
`
|
|
6345
6355
|
Git:`,
|
|
6346
6356
|
`- Stay on \`${context.githubBranch}\` for the whole task: do not check out another branch and do not create one. It was cut from \`${context.baseBranch}\`, and PRs target that automatically.`,
|
|
@@ -7502,6 +7512,11 @@ var uploadAttachmentContract = defineToolContract({
|
|
|
7502
7512
|
title: f.optional(
|
|
7503
7513
|
f.string({ desc: "Short caption posted with the file (defaults to the file name)" })
|
|
7504
7514
|
),
|
|
7515
|
+
comment: f.optional(
|
|
7516
|
+
f.string({
|
|
7517
|
+
desc: "Alias of `title` (the external conveyor-mcp surface names this field `comment`). Provide at most one of the two."
|
|
7518
|
+
})
|
|
7519
|
+
),
|
|
7505
7520
|
tags: attachmentTags
|
|
7506
7521
|
}
|
|
7507
7522
|
},
|
|
@@ -7514,6 +7529,11 @@ var uploadAttachmentContract = defineToolContract({
|
|
|
7514
7529
|
comment: f.optional(
|
|
7515
7530
|
f.string({ desc: "When set, also posts the attachment to the task chat with this text" })
|
|
7516
7531
|
),
|
|
7532
|
+
title: f.optional(
|
|
7533
|
+
f.string({
|
|
7534
|
+
desc: "Alias of `comment` (the in-pod agent surface names this field `title`). Provide at most one of the two."
|
|
7535
|
+
})
|
|
7536
|
+
),
|
|
7517
7537
|
tags: attachmentTags,
|
|
7518
7538
|
mimeType: f.optional(
|
|
7519
7539
|
f.string({ desc: "Override the mime type inferred from the file extension" })
|
|
@@ -7573,7 +7593,7 @@ var agentOption = f.object(
|
|
|
7573
7593
|
var createDecisionContract = defineToolContract({
|
|
7574
7594
|
name: "create_decision",
|
|
7575
7595
|
agent: {
|
|
7576
|
-
description: `Raise a product decision: a fork the plan cannot be right without. Two or three options, one default, a deadline. Run list_decisions FIRST \u2014 an answered decision is the answer, and re-asking it wastes everybody's time. Do NOT file a decision for an assumption you can simply state: an assumed behaviour belongs in the plan as a GIVEN / WHEN / THEN line under "Assumed behaviours". Returns the decision
|
|
7596
|
+
description: `Raise a product decision: a fork the plan cannot be right without. Two or three options, one default, a deadline. Run list_decisions FIRST \u2014 an answered decision is the answer, and re-asking it wastes everybody's time. Do NOT file a decision for an assumption you can simply state: an assumed behaviour belongs in the plan as a GIVEN / WHEN / THEN line under "Assumed behaviours". Returns the decision in full (the same JSON as get_decision, with option ids and the deadline) plus which cards were linked.`,
|
|
7577
7597
|
fields: {
|
|
7578
7598
|
question: f.string({
|
|
7579
7599
|
min: 1,
|
|
@@ -7610,6 +7630,16 @@ var createDecisionContract = defineToolContract({
|
|
|
7610
7630
|
),
|
|
7611
7631
|
raised_by_pr: f.optional(
|
|
7612
7632
|
f.number({ int: true, min: 1, desc: "PR number that surfaced the question" })
|
|
7633
|
+
),
|
|
7634
|
+
raised_by_card: f.optional(
|
|
7635
|
+
f.string({
|
|
7636
|
+
desc: "Slug or id of the card that raised the question. A link back, not a block \u2014 use impacted_cards to block a card."
|
|
7637
|
+
})
|
|
7638
|
+
),
|
|
7639
|
+
deadline_at: f.optional(
|
|
7640
|
+
f.string({
|
|
7641
|
+
desc: "Absolute deadline as an ISO 8601 timestamp, e.g. 2026-09-25T17:00:00Z. Wins over deadline_hours when both are given; must be in the future and within 720 hours."
|
|
7642
|
+
})
|
|
7613
7643
|
)
|
|
7614
7644
|
}
|
|
7615
7645
|
},
|
|
@@ -7633,6 +7663,16 @@ var createDecisionContract = defineToolContract({
|
|
|
7633
7663
|
tagNames: f.optional(f.array(f.string(), { desc: "Tag names to categorize the decision" })),
|
|
7634
7664
|
raisedByPr: f.optional(
|
|
7635
7665
|
f.number({ int: true, min: 1, desc: "PR number that surfaced the question" })
|
|
7666
|
+
),
|
|
7667
|
+
raisedByCard: f.optional(
|
|
7668
|
+
f.string({
|
|
7669
|
+
desc: "Slug or id of the card that raised the question. A link back, not a block \u2014 use impactedCards to block a card."
|
|
7670
|
+
})
|
|
7671
|
+
),
|
|
7672
|
+
deadlineAt: f.optional(
|
|
7673
|
+
f.string({
|
|
7674
|
+
desc: "Absolute deadline (ISO 8601). Wins over deadlineHours when both are given; must be in the future and within 720 hours."
|
|
7675
|
+
})
|
|
7636
7676
|
)
|
|
7637
7677
|
}
|
|
7638
7678
|
}
|
|
@@ -7640,7 +7680,7 @@ var createDecisionContract = defineToolContract({
|
|
|
7640
7680
|
var listDecisionsContract = defineToolContract({
|
|
7641
7681
|
name: "list_decisions",
|
|
7642
7682
|
agent: {
|
|
7643
|
-
description: `Search this project's decisions. Run this BEFORE planning anything in an area: "have we already decided how X behaves?"
|
|
7683
|
+
description: `Search this project's decisions. Run this BEFORE planning anything in an area: "have we already decided how X behaves?" Each row carries the summary, who raised it (and from which card or PR), when, and \u2014 once settled \u2014 the resolution, so the list doubles as the decision log. Filter by card to see what is blocking a specific card.`,
|
|
7644
7684
|
fields: {
|
|
7645
7685
|
status: f.optional(
|
|
7646
7686
|
f.enum(STATUS_LABELS, {
|
|
@@ -7672,7 +7712,7 @@ var listDecisionsContract = defineToolContract({
|
|
|
7672
7712
|
var getDecisionContract = defineToolContract({
|
|
7673
7713
|
name: "get_decision",
|
|
7674
7714
|
agent: {
|
|
7675
|
-
description: "Read one decision in full: its context, every option with its GIVEN / WHEN / THEN, every vote with who cast it and why, the running tally, the cards it blocks, and the resolution once there is one. Use read_task_chat with the same id for the discussion thread.",
|
|
7715
|
+
description: "Read one decision in full: its summary and context, who raised it and from where, every option with its GIVEN / WHEN / THEN, every vote with who cast it and why, the running tally, the cards it blocks, and the resolution once there is one. Use read_task_chat with the same id for the discussion thread.",
|
|
7676
7716
|
fields: {
|
|
7677
7717
|
decision: f.string({ min: 1, desc: "Decision slug or id" })
|
|
7678
7718
|
}
|
|
@@ -7737,10 +7777,32 @@ var resolveDecisionContract = defineToolContract({
|
|
|
7737
7777
|
var updateDecisionContract = defineToolContract({
|
|
7738
7778
|
name: "update_decision",
|
|
7739
7779
|
agent: {
|
|
7740
|
-
description: "Change an open decision:
|
|
7780
|
+
description: "Change an open decision: fix the question, summary, context or tags, rewrite the options while nobody has voted, move the deadline, link or unlink the cards it blocks, or cancel it. Cancelling un-blocks the impacted cards without answering the question, so only do it when the question stopped mattering.",
|
|
7741
7781
|
fields: {
|
|
7742
7782
|
decision: f.string({ min: 1, desc: "Decision slug or id" }),
|
|
7783
|
+
question: f.optional(f.string({ min: 1, desc: "New question, one plain sentence" })),
|
|
7784
|
+
summary: f.optional(
|
|
7785
|
+
f.string({ desc: cardDescriptionDesc("New one- or two-sentence summary") })
|
|
7786
|
+
),
|
|
7787
|
+
context: f.optional(f.string({ min: 1, desc: "New markdown background" })),
|
|
7788
|
+
options: f.optional(
|
|
7789
|
+
f.array(agentOption, {
|
|
7790
|
+
min: 2,
|
|
7791
|
+
desc: "Replacement options. Accepted only while the decision has no votes \u2014 once somebody has voted, cancel it and raise a new one instead. Send default_option with it: the old default no longer exists."
|
|
7792
|
+
})
|
|
7793
|
+
),
|
|
7794
|
+
default_option: f.optional(
|
|
7795
|
+
f.number({ int: true, min: 1, max: 3, desc: "1-based position of the new default" })
|
|
7796
|
+
),
|
|
7797
|
+
tag_names: f.optional(
|
|
7798
|
+
f.array(f.string(), { desc: "Replacement tag names (replaces the current set)" })
|
|
7799
|
+
),
|
|
7743
7800
|
deadline_hours: f.optional(f.number({ desc: "New deadline, in hours from now. 1 to 720." })),
|
|
7801
|
+
deadline_at: f.optional(
|
|
7802
|
+
f.string({
|
|
7803
|
+
desc: "New absolute deadline (ISO 8601). Wins over deadline_hours; must be in the future and within 720 hours."
|
|
7804
|
+
})
|
|
7805
|
+
),
|
|
7744
7806
|
add_impacted_cards: f.optional(
|
|
7745
7807
|
f.array(f.string(), { desc: "Slugs or ids of cards to block on this decision" })
|
|
7746
7808
|
),
|
|
@@ -7755,13 +7817,29 @@ var updateDecisionContract = defineToolContract({
|
|
|
7755
7817
|
}
|
|
7756
7818
|
},
|
|
7757
7819
|
mcp: {
|
|
7758
|
-
description: "Change an open Conveyor decision:
|
|
7820
|
+
description: "Change an open Conveyor decision: fix the question, summary, context or tags, rewrite the options while nobody has voted, move the deadline, link or unlink impacted cards, cancel it, or reopen a settled one.",
|
|
7759
7821
|
fields: {
|
|
7760
7822
|
projectId: mcpProjectId,
|
|
7761
7823
|
decision: f.string({ desc: "Decision slug or id" }),
|
|
7824
|
+
question: f.optional(f.string({ desc: "New question" })),
|
|
7825
|
+
summary: f.optional(f.string({ desc: cardDescriptionDesc("New summary") })),
|
|
7826
|
+
context: f.optional(f.string({ desc: "New markdown background" })),
|
|
7827
|
+
options: f.optional(
|
|
7828
|
+
f.array(agentOption, {
|
|
7829
|
+
min: 2,
|
|
7830
|
+
desc: "Replacement options. Accepted only while the decision has no votes. Send defaultOption with it: the old default no longer exists."
|
|
7831
|
+
})
|
|
7832
|
+
),
|
|
7833
|
+
defaultOption: f.optional(
|
|
7834
|
+
f.number({ int: true, min: 1, max: 3, desc: "1-based position of the new default" })
|
|
7835
|
+
),
|
|
7836
|
+
tagNames: f.optional(f.array(f.string(), { desc: "Replacement tag names" })),
|
|
7762
7837
|
deadlineHours: f.optional(
|
|
7763
7838
|
f.number({ min: 1, max: 720, desc: "New deadline, in hours from now" })
|
|
7764
7839
|
),
|
|
7840
|
+
deadlineAt: f.optional(
|
|
7841
|
+
f.string({ desc: "New absolute deadline (ISO 8601). Wins over deadlineHours." })
|
|
7842
|
+
),
|
|
7765
7843
|
addImpactedCards: f.optional(
|
|
7766
7844
|
f.array(f.string(), { desc: "Slugs or ids of cards to block on this decision" })
|
|
7767
7845
|
),
|
|
@@ -8506,10 +8584,11 @@ function buildReadTaskChatTool(connection) {
|
|
|
8506
8584
|
taskId: task_id
|
|
8507
8585
|
});
|
|
8508
8586
|
return textResult(JSON.stringify(messages, null, 2));
|
|
8509
|
-
} catch {
|
|
8587
|
+
} catch (error) {
|
|
8510
8588
|
return textResult(
|
|
8511
8589
|
JSON.stringify({
|
|
8512
|
-
|
|
8590
|
+
error: `Failed to read chat: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
8591
|
+
note: "Usually transient \u2014 retry. Chat history up to this session's start was provided in the initial context."
|
|
8513
8592
|
})
|
|
8514
8593
|
);
|
|
8515
8594
|
}
|
|
@@ -9293,7 +9372,8 @@ Paste this into the PR description to show it inline:
|
|
|
9293
9372
|
${snippet}`;
|
|
9294
9373
|
}
|
|
9295
9374
|
function buildUploadAttachmentTool(connection, config) {
|
|
9296
|
-
return defineContractTool(uploadAttachmentContract, async ({ path, title, tags }) => {
|
|
9375
|
+
return defineContractTool(uploadAttachmentContract, async ({ path, title, comment, tags }) => {
|
|
9376
|
+
const caption = title ?? comment;
|
|
9297
9377
|
try {
|
|
9298
9378
|
const filePath = isAbsolute(path) ? path : join16(config.workspaceDir, path);
|
|
9299
9379
|
const mimeType = inferMimeType(filePath);
|
|
@@ -9329,11 +9409,11 @@ function buildUploadAttachmentTool(connection, config) {
|
|
|
9329
9409
|
const result = await connection.call("confirmFileUpload", {
|
|
9330
9410
|
sessionId: connection.sessionId,
|
|
9331
9411
|
fileId,
|
|
9332
|
-
title,
|
|
9412
|
+
title: caption,
|
|
9333
9413
|
tags
|
|
9334
9414
|
});
|
|
9335
9415
|
return textResult(
|
|
9336
|
-
`Uploaded ${fileName} (${info.size} bytes) and posted it to the task chat${
|
|
9416
|
+
`Uploaded ${fileName} (${info.size} bytes) and posted it to the task chat${caption ? ` with caption "${caption}"` : ""}. File ID: ${result.fileId}${describeTags(result.appliedTags, result.unknownTags)}${describeEmbed(fileName, result.downloadUrl)}`
|
|
9337
9417
|
);
|
|
9338
9418
|
} catch (error) {
|
|
9339
9419
|
return textResult(
|
|
@@ -10765,14 +10845,19 @@ function raiseAndVoteTools(connection, projectId) {
|
|
|
10765
10845
|
includeCurrentCard: input.include_current_card,
|
|
10766
10846
|
sessionId: connection.sessionId,
|
|
10767
10847
|
tagNames: input.tag_names,
|
|
10768
|
-
raisedByPr: input.raised_by_pr
|
|
10848
|
+
raisedByPr: input.raised_by_pr,
|
|
10849
|
+
raisedByCard: input.raised_by_card,
|
|
10850
|
+
deadlineAt: input.deadline_at
|
|
10769
10851
|
});
|
|
10770
|
-
const
|
|
10852
|
+
const { impactedLinked, refused, ...detail } = res;
|
|
10853
|
+
const refusedLine = refused.length > 0 ? `
|
|
10771
10854
|
|
|
10772
|
-
Not linked: ${
|
|
10855
|
+
Not linked: ${refused.map((r) => `${r.ref} (${r.reason})`).join("; ")}` : "";
|
|
10773
10856
|
return textResult(
|
|
10774
10857
|
`Raised decision ${res.slug} \u2014 ${res.url}
|
|
10775
|
-
Blocking ${
|
|
10858
|
+
Blocking ${impactedLinked.length} card(s).${refusedLine}
|
|
10859
|
+
|
|
10860
|
+
${JSON.stringify(detail, null, 2)}`
|
|
10776
10861
|
);
|
|
10777
10862
|
} catch (error) {
|
|
10778
10863
|
return errText5("Failed to raise the decision", error);
|
|
@@ -10817,7 +10902,14 @@ function settleTools(connection, projectId) {
|
|
|
10817
10902
|
const res = await connection.call("updateProjectDecision", {
|
|
10818
10903
|
projectId,
|
|
10819
10904
|
decision: input.decision,
|
|
10905
|
+
question: input.question,
|
|
10906
|
+
summary: input.summary,
|
|
10907
|
+
context: input.context,
|
|
10908
|
+
options: input.options,
|
|
10909
|
+
defaultOption: input.default_option,
|
|
10910
|
+
tagNames: input.tag_names,
|
|
10820
10911
|
deadlineHours: input.deadline_hours,
|
|
10912
|
+
deadlineAt: input.deadline_at,
|
|
10821
10913
|
addImpactedCards: input.add_impacted_cards,
|
|
10822
10914
|
removeImpactedCards: input.remove_impacted_cards,
|
|
10823
10915
|
status: input.status
|
|
@@ -11216,6 +11308,26 @@ function safeVoid(promise, context) {
|
|
|
11216
11308
|
});
|
|
11217
11309
|
}
|
|
11218
11310
|
}
|
|
11311
|
+
function addTurnUsage(total, usage) {
|
|
11312
|
+
const next = {
|
|
11313
|
+
inputTokens: total.inputTokens + (usage.input_tokens ?? 0),
|
|
11314
|
+
outputTokens: total.outputTokens + (usage.output_tokens ?? 0),
|
|
11315
|
+
cacheReadInputTokens: total.cacheReadInputTokens + (usage.cache_read_input_tokens ?? 0),
|
|
11316
|
+
cacheCreationInputTokens: total.cacheCreationInputTokens + (usage.cache_creation_input_tokens ?? 0),
|
|
11317
|
+
totalTokens: 0
|
|
11318
|
+
};
|
|
11319
|
+
next.totalTokens = next.inputTokens + next.outputTokens + next.cacheReadInputTokens + next.cacheCreationInputTokens;
|
|
11320
|
+
return next;
|
|
11321
|
+
}
|
|
11322
|
+
function emptyTurnUsage() {
|
|
11323
|
+
return {
|
|
11324
|
+
inputTokens: 0,
|
|
11325
|
+
outputTokens: 0,
|
|
11326
|
+
cacheReadInputTokens: 0,
|
|
11327
|
+
cacheCreationInputTokens: 0,
|
|
11328
|
+
totalTokens: 0
|
|
11329
|
+
};
|
|
11330
|
+
}
|
|
11219
11331
|
function epochSecondsToISO(value) {
|
|
11220
11332
|
if (typeof value === "string") return value;
|
|
11221
11333
|
if (typeof value !== "number" || value <= 0) return void 0;
|
|
@@ -11297,11 +11409,16 @@ function emitContextUpdate(modelUsage, host, context, lastAssistantUsage) {
|
|
|
11297
11409
|
});
|
|
11298
11410
|
}
|
|
11299
11411
|
}
|
|
11300
|
-
function handleSuccessResult(event, host, context, startTime, lastAssistantUsage) {
|
|
11412
|
+
function handleSuccessResult(event, host, context, startTime, lastAssistantUsage, turnUsage) {
|
|
11301
11413
|
const durationMs = Date.now() - startTime;
|
|
11302
11414
|
const summary = event.result || "Task completed.";
|
|
11303
11415
|
const retriable = isRetriableMessage(summary);
|
|
11304
|
-
host.connection.sendEvent({
|
|
11416
|
+
host.connection.sendEvent({
|
|
11417
|
+
type: "completed",
|
|
11418
|
+
summary,
|
|
11419
|
+
durationMs,
|
|
11420
|
+
...turnUsage && turnUsage.totalTokens > 0 ? { usage: turnUsage } : {}
|
|
11421
|
+
});
|
|
11305
11422
|
const { modelUsage } = event;
|
|
11306
11423
|
if (modelUsage && typeof modelUsage === "object") {
|
|
11307
11424
|
emitContextUpdate(modelUsage, host, context, lastAssistantUsage);
|
|
@@ -11322,7 +11439,7 @@ function handleErrorResult(event, host) {
|
|
|
11322
11439
|
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
11323
11440
|
return { retriable };
|
|
11324
11441
|
}
|
|
11325
|
-
function handleResultEvent(event, host, context, startTime, lastAssistantUsage) {
|
|
11442
|
+
function handleResultEvent(event, host, context, startTime, lastAssistantUsage, turnUsage) {
|
|
11326
11443
|
const resultSummary = event.subtype === "success" ? event.result : event.errors.join(", ");
|
|
11327
11444
|
if (event.subtype === "success") {
|
|
11328
11445
|
const result2 = handleSuccessResult(
|
|
@@ -11330,15 +11447,16 @@ function handleResultEvent(event, host, context, startTime, lastAssistantUsage)
|
|
|
11330
11447
|
host,
|
|
11331
11448
|
context,
|
|
11332
11449
|
startTime,
|
|
11333
|
-
lastAssistantUsage
|
|
11450
|
+
lastAssistantUsage,
|
|
11451
|
+
turnUsage
|
|
11334
11452
|
);
|
|
11335
11453
|
return { ...result2, resultSummary };
|
|
11336
11454
|
}
|
|
11337
11455
|
const result = handleErrorResult(event, host);
|
|
11338
11456
|
return { ...result, resultSummary };
|
|
11339
11457
|
}
|
|
11340
|
-
async function emitResultEvent(event, host, context, startTime, lastAssistantUsage) {
|
|
11341
|
-
const result = handleResultEvent(event, host, context, startTime, lastAssistantUsage);
|
|
11458
|
+
async function emitResultEvent(event, host, context, startTime, lastAssistantUsage, turnUsage) {
|
|
11459
|
+
const result = handleResultEvent(event, host, context, startTime, lastAssistantUsage, turnUsage);
|
|
11342
11460
|
const durationMs = Date.now() - startTime;
|
|
11343
11461
|
if (event.subtype === "success") {
|
|
11344
11462
|
const successEvent = event;
|
|
@@ -11346,7 +11464,8 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
11346
11464
|
await host.callbacks.onEvent({
|
|
11347
11465
|
type: "completed",
|
|
11348
11466
|
summary,
|
|
11349
|
-
durationMs
|
|
11467
|
+
durationMs,
|
|
11468
|
+
...turnUsage && turnUsage.totalTokens > 0 ? { usage: turnUsage } : {}
|
|
11350
11469
|
});
|
|
11351
11470
|
} else if (!result.staleSession) {
|
|
11352
11471
|
const errorEvent = event;
|
|
@@ -11452,13 +11571,20 @@ async function handleAssistantCase(event, host, turnToolCalls) {
|
|
|
11452
11571
|
const msgUsage = event.message.usage;
|
|
11453
11572
|
return msgUsage ?? void 0;
|
|
11454
11573
|
}
|
|
11455
|
-
async function handleResultCase(event, host, context, startTime, isTyping, lastAssistantUsage) {
|
|
11574
|
+
async function handleResultCase(event, host, context, startTime, isTyping, lastAssistantUsage, turnUsage) {
|
|
11456
11575
|
let stoppedTyping = false;
|
|
11457
11576
|
if (isTyping) {
|
|
11458
11577
|
host.connection.sendTypingStop();
|
|
11459
11578
|
stoppedTyping = true;
|
|
11460
11579
|
}
|
|
11461
|
-
const resultInfo = await emitResultEvent(
|
|
11580
|
+
const resultInfo = await emitResultEvent(
|
|
11581
|
+
event,
|
|
11582
|
+
host,
|
|
11583
|
+
context,
|
|
11584
|
+
startTime,
|
|
11585
|
+
lastAssistantUsage,
|
|
11586
|
+
turnUsage
|
|
11587
|
+
);
|
|
11462
11588
|
return {
|
|
11463
11589
|
retriable: resultInfo.retriable,
|
|
11464
11590
|
resultSummary: resultInfo.resultSummary,
|
|
@@ -11562,7 +11688,10 @@ async function processAssistantCase(event, host, state) {
|
|
|
11562
11688
|
state.isTyping = true;
|
|
11563
11689
|
}
|
|
11564
11690
|
const usage = await handleAssistantCase(event, host, state.turnToolCalls);
|
|
11565
|
-
if (usage)
|
|
11691
|
+
if (usage) {
|
|
11692
|
+
state.lastAssistantUsage = usage;
|
|
11693
|
+
state.turnUsage = addTurnUsage(state.turnUsage, usage);
|
|
11694
|
+
}
|
|
11566
11695
|
if (!state.sawApiError || event.isApiError && !state.terminalApiError) {
|
|
11567
11696
|
const fullText = event.message.content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
11568
11697
|
if (API_ERROR_PATTERN.test(fullText)) {
|
|
@@ -11581,7 +11710,8 @@ async function processResultCase(event, host, context, startTime, state) {
|
|
|
11581
11710
|
context,
|
|
11582
11711
|
startTime,
|
|
11583
11712
|
state.isTyping,
|
|
11584
|
-
state.lastAssistantUsage
|
|
11713
|
+
state.lastAssistantUsage,
|
|
11714
|
+
state.turnUsage
|
|
11585
11715
|
);
|
|
11586
11716
|
if (info.stoppedTyping) state.isTyping = false;
|
|
11587
11717
|
state.retriable = info.retriable;
|
|
@@ -11614,6 +11744,7 @@ async function processEvents(events, context, host) {
|
|
|
11614
11744
|
staleSession: void 0,
|
|
11615
11745
|
authError: void 0,
|
|
11616
11746
|
lastAssistantUsage: void 0,
|
|
11747
|
+
turnUsage: emptyTurnUsage(),
|
|
11617
11748
|
turnToolCalls: [],
|
|
11618
11749
|
questionPending: false
|
|
11619
11750
|
};
|
|
@@ -11693,6 +11824,9 @@ function fallbackResetIso(rateLimitType, now = Date.now()) {
|
|
|
11693
11824
|
return new Date(now + (weekly ? TWENTY_FOUR_HOURS_MS : FIVE_HOURS_MS)).toISOString();
|
|
11694
11825
|
}
|
|
11695
11826
|
|
|
11827
|
+
// src/execution/tool-access.ts
|
|
11828
|
+
import { isAbsolute as isAbsolute3, posix } from "path";
|
|
11829
|
+
|
|
11696
11830
|
// src/execution/task-property-utils.ts
|
|
11697
11831
|
function collectMissingProps(taskProps) {
|
|
11698
11832
|
const missing = [];
|
|
@@ -11835,6 +11969,12 @@ function isPlanFile(input) {
|
|
|
11835
11969
|
const filePath = String(input.file_path ?? input.path ?? "");
|
|
11836
11970
|
return filePath.includes(".claude/plans/");
|
|
11837
11971
|
}
|
|
11972
|
+
function isScratchPath(input) {
|
|
11973
|
+
const filePath = String(input.file_path ?? input.path ?? "");
|
|
11974
|
+
if (!isAbsolute3(filePath)) return false;
|
|
11975
|
+
return posix.normalize(filePath).startsWith("/tmp/");
|
|
11976
|
+
}
|
|
11977
|
+
var ARTIFACT_DENIED = "Publish deliverables on the card with upload_attachment; the Artifact tool is not available in Conveyor sessions.";
|
|
11838
11978
|
var READ_ONLY_BLOCKED_BASH = [
|
|
11839
11979
|
{ name: "git commit", re: /\bgit\s+(?:-\S+\s+)*commit\b/ },
|
|
11840
11980
|
{ name: "git push", re: /\bgit\s+(?:-\S+\s+)*push\b/ },
|
|
@@ -11860,12 +12000,12 @@ function handleReadOnlyToolAccess(toolName, input) {
|
|
|
11860
12000
|
return { behavior: "deny", message: CHANNEL_POST_DENIED };
|
|
11861
12001
|
}
|
|
11862
12002
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
11863
|
-
if (isPlanFile(input)) {
|
|
12003
|
+
if (isPlanFile(input) || isScratchPath(input)) {
|
|
11864
12004
|
return { behavior: "allow", updatedInput: input };
|
|
11865
12005
|
}
|
|
11866
12006
|
return {
|
|
11867
12007
|
behavior: "deny",
|
|
11868
|
-
message: "Discovery mode is read-only. File writes are restricted to plan files."
|
|
12008
|
+
message: "Discovery mode is read-only. File writes are restricted to plan files and /tmp/ scratch files \u2014 write a deliverable under /tmp/ and attach it with upload_attachment."
|
|
11869
12009
|
};
|
|
11870
12010
|
}
|
|
11871
12011
|
if (toolName === "Bash") {
|
|
@@ -12024,7 +12164,7 @@ var DENIAL_FORCE_STOP_THRESHOLD = 8;
|
|
|
12024
12164
|
function handleDenialEscalation(host, consecutiveDenials) {
|
|
12025
12165
|
if (consecutiveDenials === DENIAL_WARNING_THRESHOLD) {
|
|
12026
12166
|
host.connection.postChatMessage(
|
|
12027
|
-
`\u26A0\uFE0F Multiple tool denials detected. You are in ${host.agentMode} mode \u2014 file writes outside .claude/plans/ are not permitted. Focus on creating a plan instead of implementing code changes.`
|
|
12167
|
+
`\u26A0\uFE0F Multiple tool denials detected. You are in ${host.agentMode} mode \u2014 file writes outside .claude/plans/ and /tmp/ are not permitted. Focus on creating a plan instead of implementing code changes.`
|
|
12028
12168
|
);
|
|
12029
12169
|
}
|
|
12030
12170
|
if (consecutiveDenials >= DENIAL_FORCE_STOP_THRESHOLD) {
|
|
@@ -12070,6 +12210,9 @@ function checkToolLoop(host, toolName, input) {
|
|
|
12070
12210
|
return { behavior: "deny", message: buildRepeatLoopMessage(repeats, isHeavyGateActive()) };
|
|
12071
12211
|
}
|
|
12072
12212
|
function resolveToolAccess(host, toolName, input) {
|
|
12213
|
+
if (toolName === "Artifact") {
|
|
12214
|
+
return { behavior: "deny", message: ARTIFACT_DENIED };
|
|
12215
|
+
}
|
|
12073
12216
|
switch (host.agentMode) {
|
|
12074
12217
|
case "discovery":
|
|
12075
12218
|
case "help":
|
|
@@ -13784,6 +13927,100 @@ var BackgroundWorkTracker = class {
|
|
|
13784
13927
|
}
|
|
13785
13928
|
};
|
|
13786
13929
|
|
|
13930
|
+
// src/runner/scheduled-wakeup.ts
|
|
13931
|
+
var SCHEDULE_WAKEUP_TOOL = "schedulewakeup";
|
|
13932
|
+
var SCHEDULED_WAKEUP_MIN_DELAY_SECONDS = 60;
|
|
13933
|
+
var SCHEDULED_WAKEUP_MAX_DELAY_SECONDS = 3600;
|
|
13934
|
+
var SCHEDULED_WAKEUP_GRACE_MS = 2 * 60 * 1e3;
|
|
13935
|
+
function asRecord3(input) {
|
|
13936
|
+
if (typeof input === "string") {
|
|
13937
|
+
try {
|
|
13938
|
+
const parsed = JSON.parse(input);
|
|
13939
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
13940
|
+
} catch {
|
|
13941
|
+
return null;
|
|
13942
|
+
}
|
|
13943
|
+
}
|
|
13944
|
+
return typeof input === "object" && input !== null ? input : null;
|
|
13945
|
+
}
|
|
13946
|
+
function parseScheduledWakeup(tool2, input, now = Date.now()) {
|
|
13947
|
+
if (tool2.toLowerCase() !== SCHEDULE_WAKEUP_TOOL) return null;
|
|
13948
|
+
const record = asRecord3(input);
|
|
13949
|
+
if (!record) return null;
|
|
13950
|
+
if (record.stop === true) return null;
|
|
13951
|
+
const raw = record.delaySeconds;
|
|
13952
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) return null;
|
|
13953
|
+
const delaySeconds = Math.min(
|
|
13954
|
+
SCHEDULED_WAKEUP_MAX_DELAY_SECONDS,
|
|
13955
|
+
Math.max(SCHEDULED_WAKEUP_MIN_DELAY_SECONDS, raw)
|
|
13956
|
+
);
|
|
13957
|
+
return { delaySeconds, deadline: now + delaySeconds * 1e3 + SCHEDULED_WAKEUP_GRACE_MS };
|
|
13958
|
+
}
|
|
13959
|
+
function isScheduledWakeupStop(tool2, input) {
|
|
13960
|
+
if (tool2.toLowerCase() !== SCHEDULE_WAKEUP_TOOL) return false;
|
|
13961
|
+
return asRecord3(input)?.stop === true;
|
|
13962
|
+
}
|
|
13963
|
+
var ScheduledWakeupTracker = class {
|
|
13964
|
+
/** Absolute expiry (epoch ms) of the armed wakeup; null when none. */
|
|
13965
|
+
deadline = null;
|
|
13966
|
+
onChange;
|
|
13967
|
+
log;
|
|
13968
|
+
constructor(options = {}) {
|
|
13969
|
+
this.onChange = options.onChange;
|
|
13970
|
+
this.log = options.log ?? ((message) => process.stderr.write(message));
|
|
13971
|
+
}
|
|
13972
|
+
/**
|
|
13973
|
+
* Feed one `tool_use` from the turn stream. Returns true when it changed the
|
|
13974
|
+
* armed state (test/diagnostic signal; callers can ignore it).
|
|
13975
|
+
*/
|
|
13976
|
+
noteToolUse(tool2, input, now = Date.now()) {
|
|
13977
|
+
if (isScheduledWakeupStop(tool2, input)) {
|
|
13978
|
+
if (this.deadline === null) return false;
|
|
13979
|
+
this.deadline = null;
|
|
13980
|
+
this.log("[conveyor-agent] scheduled wakeup stopped \u2014 heartbeats may report idle again\n");
|
|
13981
|
+
this.onChange?.();
|
|
13982
|
+
return true;
|
|
13983
|
+
}
|
|
13984
|
+
const arm = parseScheduledWakeup(tool2, input, now);
|
|
13985
|
+
if (!arm) return false;
|
|
13986
|
+
this.deadline = arm.deadline;
|
|
13987
|
+
this.log(
|
|
13988
|
+
`[conveyor-agent] scheduled wakeup armed (${arm.delaySeconds}s) \u2014 heartbeats stay non-idle until it fires
|
|
13989
|
+
`
|
|
13990
|
+
);
|
|
13991
|
+
this.onChange?.();
|
|
13992
|
+
return true;
|
|
13993
|
+
}
|
|
13994
|
+
/** Is a non-expired wakeup armed? */
|
|
13995
|
+
hasPending(now = Date.now()) {
|
|
13996
|
+
this.prune(now);
|
|
13997
|
+
return this.deadline !== null;
|
|
13998
|
+
}
|
|
13999
|
+
/** Absolute expiry of the armed wakeup, or null when none is armed. Callers
|
|
14000
|
+
* bound their own deferrals by this rather than by a fixed cap — a one-hour
|
|
14001
|
+
* arm outlives `BACKGROUND_WORK_MAX_MS`. */
|
|
14002
|
+
pendingDeadline(now = Date.now()) {
|
|
14003
|
+
this.prune(now);
|
|
14004
|
+
return this.deadline;
|
|
14005
|
+
}
|
|
14006
|
+
/** Drop the arm (a turn started, or the session is shutting down). */
|
|
14007
|
+
clear() {
|
|
14008
|
+
if (this.deadline === null) return;
|
|
14009
|
+
this.deadline = null;
|
|
14010
|
+
this.onChange?.();
|
|
14011
|
+
}
|
|
14012
|
+
/** Expire a deadline that has passed, loudly — the wakeup should have fired
|
|
14013
|
+
* a turn by now, so a tripped expiry is worth seeing in the log. */
|
|
14014
|
+
prune(now) {
|
|
14015
|
+
if (this.deadline === null || this.deadline > now) return;
|
|
14016
|
+
this.deadline = null;
|
|
14017
|
+
this.log(
|
|
14018
|
+
"[conveyor-agent] scheduled wakeup deadline passed with no turn \u2014 reporting idle again\n"
|
|
14019
|
+
);
|
|
14020
|
+
this.onChange?.();
|
|
14021
|
+
}
|
|
14022
|
+
};
|
|
14023
|
+
|
|
13787
14024
|
// src/runner/live-children.ts
|
|
13788
14025
|
function findLiveChild(sources) {
|
|
13789
14026
|
for (const source of sources) {
|
|
@@ -13851,6 +14088,13 @@ var SessionRunner = class _SessionRunner {
|
|
|
13851
14088
|
backgroundWork = new BackgroundWorkTracker({
|
|
13852
14089
|
onChange: () => this.refreshLoopStatus()
|
|
13853
14090
|
});
|
|
14091
|
+
/** An armed `ScheduleWakeup` (the dynamic `/loop` mode): the turn ended, but
|
|
14092
|
+
* the agent asked to be re-invoked at a deadline. Same liveness contract as
|
|
14093
|
+
* `backgroundWork` — while it reports pending, heartbeats go out non-idle so
|
|
14094
|
+
* the reconciler cannot sleep the pod before the wakeup fires. */
|
|
14095
|
+
scheduledWakeup = new ScheduledWakeupTracker({
|
|
14096
|
+
onChange: () => this.refreshLoopStatus()
|
|
14097
|
+
});
|
|
13854
14098
|
/** Boot supervisor handle, set post-construction once it's known (it is
|
|
13855
14099
|
* started alongside connect(), before the runner itself exists in some
|
|
13856
14100
|
* call sites) — notified when the core loop goes live so it can release
|
|
@@ -13895,6 +14139,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
13895
14139
|
},
|
|
13896
14140
|
onIdleTimeout: () => {
|
|
13897
14141
|
if (this.deferShutdownForLiveChild("idle")) return;
|
|
14142
|
+
if (this.deferShutdownForScheduledWakeup("idle")) return;
|
|
13898
14143
|
if (this.deferShutdownForBackgroundWork()) return;
|
|
13899
14144
|
process.stderr.write("[conveyor-agent] Idle timeout reached, stopping agent\n");
|
|
13900
14145
|
this.stopped = true;
|
|
@@ -13907,6 +14152,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
13907
14152
|
},
|
|
13908
14153
|
onDormantTimeout: () => {
|
|
13909
14154
|
if (this.deferShutdownForLiveChild("dormant")) return;
|
|
14155
|
+
if (this.deferShutdownForScheduledWakeup("dormant")) return;
|
|
13910
14156
|
process.stderr.write("[conveyor-agent] Dormant idle timeout reached, shutting down\n");
|
|
13911
14157
|
this.stopped = true;
|
|
13912
14158
|
this.queryBridge?.stop();
|
|
@@ -13966,6 +14212,50 @@ var SessionRunner = class _SessionRunner {
|
|
|
13966
14212
|
const label = timer === "idle" ? "Idle" : "Dormant idle";
|
|
13967
14213
|
process.stderr.write(
|
|
13968
14214
|
`[conveyor-agent] ${label} timeout deferred: ${live.kind} session ${live.sessionId} active
|
|
14215
|
+
`
|
|
14216
|
+
);
|
|
14217
|
+
if (timer === "idle") {
|
|
14218
|
+
this.lifecycle.startIdleTimer(recheckMs);
|
|
14219
|
+
} else {
|
|
14220
|
+
this.dormantDeadline = Date.now() + recheckMs;
|
|
14221
|
+
this.lifecycle.startDormantTimer(recheckMs);
|
|
14222
|
+
}
|
|
14223
|
+
return true;
|
|
14224
|
+
}
|
|
14225
|
+
/**
|
|
14226
|
+
* Suppress an idle or dormant shutdown while a `ScheduleWakeup` is armed.
|
|
14227
|
+
*
|
|
14228
|
+
* Both timers matter. The idle timer (30 min) kills a loop that paces itself
|
|
14229
|
+
* slower than that, and the dormant timer (60 min after a `completed` event)
|
|
14230
|
+
* kills a one-hour arm — the agent declares completion at the end of every
|
|
14231
|
+
* tick, so a dynamic loop sits dormant by construction.
|
|
14232
|
+
*
|
|
14233
|
+
* The bound is the WAKEUP'S OWN deadline, not `BACKGROUND_WORK_MAX_MS`: the
|
|
14234
|
+
* tool clamps `delaySeconds` to one hour, which already exceeds that 45-min
|
|
14235
|
+
* cap, so reusing it would shut the pod down before the longest legal arm
|
|
14236
|
+
* fired. Once the deadline passes the tracker expires it and this returns
|
|
14237
|
+
* false, so a lost timer cannot pin a pod.
|
|
14238
|
+
*/
|
|
14239
|
+
deferShutdownForScheduledWakeup(timer) {
|
|
14240
|
+
let deadline;
|
|
14241
|
+
try {
|
|
14242
|
+
deadline = this.scheduledWakeup.pendingDeadline();
|
|
14243
|
+
} catch (err) {
|
|
14244
|
+
process.stderr.write(
|
|
14245
|
+
`[conveyor-agent] Scheduled-wakeup probe failed, not deferring: ${err}
|
|
14246
|
+
`
|
|
14247
|
+
);
|
|
14248
|
+
return false;
|
|
14249
|
+
}
|
|
14250
|
+
if (deadline === null) return false;
|
|
14251
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
14252
|
+
const recheckMs = Math.min(
|
|
14253
|
+
Math.max(remainingMs, 1),
|
|
14254
|
+
Math.max(this.lifecycle.config.idleTimeoutMs, 1)
|
|
14255
|
+
);
|
|
14256
|
+
const label = timer === "idle" ? "Idle" : "Dormant idle";
|
|
14257
|
+
process.stderr.write(
|
|
14258
|
+
`[conveyor-agent] ${label} timeout deferred: scheduled wakeup armed (${Math.round(remainingMs / 1e3)}s remaining)
|
|
13969
14259
|
`
|
|
13970
14260
|
);
|
|
13971
14261
|
if (timer === "idle") {
|
|
@@ -14895,6 +15185,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
14895
15185
|
});
|
|
14896
15186
|
this.portDiscovery.stop();
|
|
14897
15187
|
this.backgroundWork.clear();
|
|
15188
|
+
this.scheduledWakeup.clear();
|
|
14898
15189
|
this.loopLag.stop();
|
|
14899
15190
|
this.lifecycle.destroy();
|
|
14900
15191
|
this.connection.disconnect();
|
|
@@ -15135,6 +15426,7 @@ ${outcome.failures.join("\n")}
|
|
|
15135
15426
|
if (this.questionPendingSince !== null && Date.now() - this.questionPendingSince < resolveQuestionGraceMs() + QUESTION_HOLD_SLACK_MS) {
|
|
15136
15427
|
return "waiting";
|
|
15137
15428
|
}
|
|
15429
|
+
if (this.scheduledWakeup.hasPending()) return "waiting";
|
|
15138
15430
|
return this.backgroundWork.hasPending() || isHeavyGateActive() ? "waiting" : "idle";
|
|
15139
15431
|
}
|
|
15140
15432
|
// ── Question grace hold ────────────────────────────────────────────
|
|
@@ -15205,11 +15497,12 @@ ${outcome.failures.join("\n")}
|
|
|
15205
15497
|
this.loopLag.setStatus(loopStatus);
|
|
15206
15498
|
return loopStatus;
|
|
15207
15499
|
}
|
|
15208
|
-
/** One `tool_use` from the turn stream — registers
|
|
15209
|
-
*
|
|
15500
|
+
/** One `tool_use` from the turn stream — registers work that outlives the
|
|
15501
|
+
* turn: a background launch, or an armed `ScheduleWakeup`. */
|
|
15210
15502
|
noteToolUseForBackgroundWork(event) {
|
|
15211
15503
|
if (event.type !== "tool_use" || typeof event.tool !== "string") return;
|
|
15212
15504
|
this.backgroundWork.noteToolUse(event.tool, event.input);
|
|
15505
|
+
this.scheduledWakeup.noteToolUse(event.tool, event.input);
|
|
15213
15506
|
}
|
|
15214
15507
|
async setState(status) {
|
|
15215
15508
|
if (status === "running") {
|
|
@@ -15250,6 +15543,7 @@ ${outcome.failures.join("\n")}
|
|
|
15250
15543
|
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
15251
15544
|
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
15252
15545
|
beginRunningEpisode() {
|
|
15546
|
+
this.scheduledWakeup.clear();
|
|
15253
15547
|
this.activeEpisodeStartedAt = Date.now();
|
|
15254
15548
|
this.backgroundDeferStartedAt = null;
|
|
15255
15549
|
this.backgroundWakeFiredThisIdle = false;
|
|
@@ -15261,6 +15555,7 @@ ${outcome.failures.join("\n")}
|
|
|
15261
15555
|
this.clearQuestionHold();
|
|
15262
15556
|
this.portDiscovery.stop();
|
|
15263
15557
|
this.backgroundWork.clear();
|
|
15558
|
+
this.scheduledWakeup.clear();
|
|
15264
15559
|
this.loopLag.stop();
|
|
15265
15560
|
this.lifecycle.destroy();
|
|
15266
15561
|
try {
|
|
@@ -26,6 +26,9 @@ function buildSpawnArgs(input) {
|
|
|
26
26
|
args.push("--permission-mode", "plan");
|
|
27
27
|
}
|
|
28
28
|
args.push("--settings", input.settingsPath);
|
|
29
|
+
if (input.disallowedTools && input.disallowedTools.length > 0) {
|
|
30
|
+
args.push("--disallowed-tools", input.disallowedTools.join(","));
|
|
31
|
+
}
|
|
29
32
|
if (input.appendSystemPrompt) {
|
|
30
33
|
args.push("--append-system-prompt", input.appendSystemPrompt);
|
|
31
34
|
}
|
|
@@ -1806,6 +1806,13 @@ var TurnEndToolCallSchema = z22.object({
|
|
|
1806
1806
|
output: z22.string().optional(),
|
|
1807
1807
|
timestamp: z22.string().optional()
|
|
1808
1808
|
}).passthrough();
|
|
1809
|
+
var AgentTurnUsageSchema = z22.object({
|
|
1810
|
+
inputTokens: z22.number(),
|
|
1811
|
+
outputTokens: z22.number(),
|
|
1812
|
+
cacheReadInputTokens: z22.number(),
|
|
1813
|
+
cacheCreationInputTokens: z22.number(),
|
|
1814
|
+
totalTokens: z22.number()
|
|
1815
|
+
});
|
|
1809
1816
|
var KnownAgentEventSchema = z22.discriminatedUnion("type", [
|
|
1810
1817
|
// ── Lifecycle / connection ────────────────────────────────────────────
|
|
1811
1818
|
z22.object({
|
|
@@ -1845,7 +1852,11 @@ var KnownAgentEventSchema = z22.discriminatedUnion("type", [
|
|
|
1845
1852
|
z22.object({
|
|
1846
1853
|
type: z22.literal("completed"),
|
|
1847
1854
|
summary: z22.string().optional(),
|
|
1848
|
-
durationMs: z22.number().optional()
|
|
1855
|
+
durationMs: z22.number().optional(),
|
|
1856
|
+
/** Tokens this turn consumed, summed from the transcript's assistant
|
|
1857
|
+
* records. Optional: an older agent build sends none, and the server
|
|
1858
|
+
* then records nothing rather than zeroing the session's totals. */
|
|
1859
|
+
usage: AgentTurnUsageSchema.optional()
|
|
1849
1860
|
}).passthrough(),
|
|
1850
1861
|
z22.object({ type: z22.literal("error"), message: z22.string() }).passthrough(),
|
|
1851
1862
|
z22.object({ type: z22.literal("agent_typing_start") }).passthrough(),
|
|
@@ -3352,6 +3363,10 @@ var CreateProjectDecisionRequestSchema = z11.object({
|
|
|
3352
3363
|
sessionId: z11.string().optional(),
|
|
3353
3364
|
tagNames: z11.array(z11.string().min(1)).max(10).optional(),
|
|
3354
3365
|
raisedByPr: z11.number().int().positive().optional(),
|
|
3366
|
+
/** Slug or id of the card that raised the question. A link back, not a block. */
|
|
3367
|
+
raisedByCard: z11.string().min(1).optional(),
|
|
3368
|
+
/** Absolute deadline (ISO 8601). Wins over `deadlineHours` when both are sent. */
|
|
3369
|
+
deadlineAt: z11.string().datetime({ offset: true }).optional(),
|
|
3355
3370
|
requestingUserId: z11.string().optional()
|
|
3356
3371
|
});
|
|
3357
3372
|
var ListProjectDecisionsRequestSchema = z11.object({
|
|
@@ -3387,7 +3402,16 @@ var ResolveProjectDecisionRequestSchema = z11.object({
|
|
|
3387
3402
|
var UpdateProjectDecisionRequestSchema = z11.object({
|
|
3388
3403
|
projectId: z11.string(),
|
|
3389
3404
|
decision: z11.string().min(1),
|
|
3405
|
+
question: z11.string().min(1).max(200).optional(),
|
|
3406
|
+
summary: z11.string().max(CARD_DESCRIPTION_MAX).optional(),
|
|
3407
|
+
context: z11.string().min(1).optional(),
|
|
3408
|
+
/** Accepted only while nobody has voted. */
|
|
3409
|
+
options: optionsInput.optional(),
|
|
3410
|
+
defaultOption: z11.number().int().min(1).max(DECISION_OPTIONS_MAX).optional(),
|
|
3411
|
+
tagNames: z11.array(z11.string().min(1)).max(10).optional(),
|
|
3390
3412
|
deadlineHours,
|
|
3413
|
+
/** Absolute deadline (ISO 8601). Wins over `deadlineHours` when both are sent. */
|
|
3414
|
+
deadlineAt: z11.string().datetime({ offset: true }).optional(),
|
|
3391
3415
|
addImpactedCards: z11.array(z11.string().min(1)).max(50).optional(),
|
|
3392
3416
|
removeImpactedCards: z11.array(z11.string().min(1)).max(50).optional(),
|
|
3393
3417
|
status: z11.enum(["Cancelled", "Open"]).optional(),
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
WorkspaceCommandSupervisor,
|
|
8
8
|
startWorkspaceCommandsAfterConnect,
|
|
9
9
|
stopWorkspaceCommands
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-IWWBG5FI.js";
|
|
11
11
|
import {
|
|
12
12
|
PtyHarness,
|
|
13
13
|
SessionRunner,
|
|
@@ -25,13 +25,13 @@ import {
|
|
|
25
25
|
runUsageProbe,
|
|
26
26
|
sampleKeyUsage,
|
|
27
27
|
usesNativeUsageReporting
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-HW6SHIL7.js";
|
|
29
29
|
import "./chunk-37J5MMQT.js";
|
|
30
30
|
import {
|
|
31
31
|
inheritedEnv,
|
|
32
32
|
resolvePtySpawn,
|
|
33
33
|
sessionTempBase
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-WCJ6HRVU.js";
|
|
35
35
|
import {
|
|
36
36
|
AgentConnection,
|
|
37
37
|
CodespacePortVisibility,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
createServiceLogger,
|
|
45
45
|
fetchBootstrap,
|
|
46
46
|
loadConveyorConfig
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-YNI2HFZE.js";
|
|
48
48
|
import "./chunk-Q4FQOJ7D.js";
|
|
49
49
|
import "./chunk-W4LZ7R6Z.js";
|
|
50
50
|
import "./chunk-372R6E4C.js";
|
|
@@ -1205,7 +1205,7 @@ function wireSpawnChildren(mode, connection, supervisors, logger7) {
|
|
|
1205
1205
|
|
|
1206
1206
|
// src/cli.ts
|
|
1207
1207
|
if (process.argv[2] === "boot") {
|
|
1208
|
-
const { runBoot } = await import("./boot-
|
|
1208
|
+
const { runBoot } = await import("./boot-65PTSADA.js");
|
|
1209
1209
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1210
1210
|
}
|
|
1211
1211
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1307,7 +1307,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
1307
1307
|
process.exit(1);
|
|
1308
1308
|
});
|
|
1309
1309
|
if (process.env.CONVEYOR_MODE === "workbench") {
|
|
1310
|
-
const { startWorkbenchServer } = await import("./server-
|
|
1310
|
+
const { startWorkbenchServer } = await import("./server-E6IYHSMN.js");
|
|
1311
1311
|
const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
|
|
1312
1312
|
const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
|
|
1313
1313
|
const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
|
|
@@ -1458,7 +1458,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
|
1458
1458
|
process.exit(1);
|
|
1459
1459
|
}
|
|
1460
1460
|
if (CONVEYOR_MODE === "serving") {
|
|
1461
|
-
const { runServingSession } = await import("./serve-boot-
|
|
1461
|
+
const { runServingSession } = await import("./serve-boot-THPACDVI.js");
|
|
1462
1462
|
exitContext.runnerMode = "serving";
|
|
1463
1463
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1464
1464
|
const outcome = await runServingSession({
|
package/dist/index.d.ts
CHANGED
|
@@ -732,6 +732,11 @@ declare class SessionRunner {
|
|
|
732
732
|
* it reports pending, heartbeats go out non-idle so the workspace activity
|
|
733
733
|
* clock keeps being bumped and the reconciler can't sleep the pod mid-gate. */
|
|
734
734
|
private readonly backgroundWork;
|
|
735
|
+
/** An armed `ScheduleWakeup` (the dynamic `/loop` mode): the turn ended, but
|
|
736
|
+
* the agent asked to be re-invoked at a deadline. Same liveness contract as
|
|
737
|
+
* `backgroundWork` — while it reports pending, heartbeats go out non-idle so
|
|
738
|
+
* the reconciler cannot sleep the pod before the wakeup fires. */
|
|
739
|
+
private readonly scheduledWakeup;
|
|
735
740
|
/** Boot supervisor handle, set post-construction once it's known (it is
|
|
736
741
|
* started alongside connect(), before the runner itself exists in some
|
|
737
742
|
* call sites) — notified when the core loop goes live so it can release
|
|
@@ -782,6 +787,21 @@ declare class SessionRunner {
|
|
|
782
787
|
* live build costs work.
|
|
783
788
|
*/
|
|
784
789
|
private deferShutdownForLiveChild;
|
|
790
|
+
/**
|
|
791
|
+
* Suppress an idle or dormant shutdown while a `ScheduleWakeup` is armed.
|
|
792
|
+
*
|
|
793
|
+
* Both timers matter. The idle timer (30 min) kills a loop that paces itself
|
|
794
|
+
* slower than that, and the dormant timer (60 min after a `completed` event)
|
|
795
|
+
* kills a one-hour arm — the agent declares completion at the end of every
|
|
796
|
+
* tick, so a dynamic loop sits dormant by construction.
|
|
797
|
+
*
|
|
798
|
+
* The bound is the WAKEUP'S OWN deadline, not `BACKGROUND_WORK_MAX_MS`: the
|
|
799
|
+
* tool clamps `delaySeconds` to one hour, which already exceeds that 45-min
|
|
800
|
+
* cap, so reusing it would shut the pod down before the longest legal arm
|
|
801
|
+
* fired. Once the deadline passes the tracker expires it and this returns
|
|
802
|
+
* false, so a lost timer cannot pin a pod.
|
|
803
|
+
*/
|
|
804
|
+
private deferShutdownForScheduledWakeup;
|
|
785
805
|
/** Epoch ms of the first idle-shutdown deferral for background work in the
|
|
786
806
|
* current idle episode; null when nothing has deferred. Reset when a turn
|
|
787
807
|
* runs, so each idle episode gets its own bounded hold. */
|
|
@@ -1101,8 +1121,8 @@ declare class SessionRunner {
|
|
|
1101
1121
|
/** Mirror the resolved status into the loop-lag buffer so the starvation-proof
|
|
1102
1122
|
* heartbeat worker reports it too. Returns what it wrote. */
|
|
1103
1123
|
private refreshLoopStatus;
|
|
1104
|
-
/** One `tool_use` from the turn stream — registers
|
|
1105
|
-
*
|
|
1124
|
+
/** One `tool_use` from the turn stream — registers work that outlives the
|
|
1125
|
+
* turn: a background launch, or an armed `ScheduleWakeup`. */
|
|
1106
1126
|
private noteToolUseForBackgroundWork;
|
|
1107
1127
|
private setState;
|
|
1108
1128
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionRunner,
|
|
3
3
|
unshallowRepo
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HW6SHIL7.js";
|
|
5
5
|
import "./chunk-37J5MMQT.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-WCJ6HRVU.js";
|
|
7
7
|
import {
|
|
8
8
|
AgentConnection,
|
|
9
9
|
GIT_TIMEOUT_MS,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stageAndCommit,
|
|
18
18
|
updateRemoteToken,
|
|
19
19
|
workspacePathExists
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YNI2HFZE.js";
|
|
21
21
|
import "./chunk-Q4FQOJ7D.js";
|
|
22
22
|
import {
|
|
23
23
|
runAuthTokenCommand,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WorkspaceCommandSupervisor
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IWWBG5FI.js";
|
|
4
4
|
import {
|
|
5
5
|
AgentConnection,
|
|
6
6
|
CodespacePortVisibility,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
createServiceLogger,
|
|
12
12
|
ensureOnTaskBranch,
|
|
13
13
|
loadConveyorConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YNI2HFZE.js";
|
|
15
15
|
import "./chunk-Q4FQOJ7D.js";
|
|
16
16
|
import "./chunk-W4LZ7R6Z.js";
|
|
17
17
|
import "./chunk-IA45XHOA.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.24",
|
|
4
4
|
"description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -157,6 +157,18 @@ and the order to verify in. Write it last, when you know which parts were hard.
|
|
|
157
157
|
It is not optional and it is not a summary. It is the only channel your context
|
|
158
158
|
has across the handoff, because handoffs carry the card and never the session.
|
|
159
159
|
|
|
160
|
+
**Deliverables beyond the plan go on the card.** A diagram, an HTML report, a
|
|
161
|
+
screenshot, or a data export the team should keep is attached with
|
|
162
|
+
`mcp__conveyor__upload_attachment` — never published as a Claude artifact, which
|
|
163
|
+
is an off-platform link the card cannot render. Write the file under `/tmp/`
|
|
164
|
+
first (a Planner session may write there; the repo checkout stays read-only) and
|
|
165
|
+
pass the caption so the file shows in the card thread rather than only in the
|
|
166
|
+
Files grid. The caption field is spelled differently per surface, and both
|
|
167
|
+
surfaces accept either name: in a pod pass `path` + `title`; on the local MCP
|
|
168
|
+
pass `taskId` + `path` + `comment` (or `title`). An HTML deliverable must be
|
|
169
|
+
self-contained — inline CSS, no `<script>`, no external assets — because the
|
|
170
|
+
card serves it under `sandbox; default-src 'none'`.
|
|
171
|
+
|
|
160
172
|
Size it: 1 SP default, 2 multi-file, 3 complex patterns/design, 5 hard. Larger
|
|
161
173
|
→ propose a pack (parent + `mcp__conveyor__create_subtask` children) instead of
|
|
162
174
|
one mega-card. **Child→child ordering rides `create_subtask`/`update_subtask`'s
|