@rallycry/conveyor-agent 10.13.3 → 10.13.9
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/{chunk-P4VWWR5O.js → chunk-PXQJ4NVO.js} +380 -171
- package/dist/chunk-PXQJ4NVO.js.map +1 -0
- package/dist/cli.js +217 -69
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/runtime/entrypoint.sh +66 -6
- package/dist/chunk-P4VWWR5O.js.map +0 -1
|
@@ -196,6 +196,8 @@ var AgentConnection = class _AgentConnection {
|
|
|
196
196
|
earlySpawnReviews = [];
|
|
197
197
|
spawnTuiCallback = null;
|
|
198
198
|
earlySpawnTuis = [];
|
|
199
|
+
probeUsageCallback = null;
|
|
200
|
+
earlyProbeUsage = false;
|
|
199
201
|
// PTY relay (S5 terminal). Single-slot callbacks, set per PtySession run.
|
|
200
202
|
ptyInputCallback = null;
|
|
201
203
|
ptyResizeCallback = null;
|
|
@@ -407,6 +409,10 @@ var AgentConnection = class _AgentConnection {
|
|
|
407
409
|
if (this.spawnTuiCallback) this.spawnTuiCallback(data);
|
|
408
410
|
else this.earlySpawnTuis.push(data);
|
|
409
411
|
});
|
|
412
|
+
this.socket.on("session:probeUsage", () => {
|
|
413
|
+
if (this.probeUsageCallback) this.probeUsageCallback();
|
|
414
|
+
else this.earlyProbeUsage = true;
|
|
415
|
+
});
|
|
410
416
|
this.socket.on("session:finalizeSnapshot", () => {
|
|
411
417
|
this.finalizeSnapshotCallback?.();
|
|
412
418
|
});
|
|
@@ -667,6 +673,15 @@ var AgentConnection = class _AgentConnection {
|
|
|
667
673
|
for (const data of this.earlySpawnTuis) callback(data);
|
|
668
674
|
this.earlySpawnTuis = [];
|
|
669
675
|
}
|
|
676
|
+
/** Register the on-demand usage-refresh handler; drains an early-buffered
|
|
677
|
+
* `session:probeUsage` that arrived before the runner was ready. */
|
|
678
|
+
onProbeUsage(callback) {
|
|
679
|
+
this.probeUsageCallback = callback;
|
|
680
|
+
if (this.earlyProbeUsage) {
|
|
681
|
+
this.earlyProbeUsage = false;
|
|
682
|
+
callback();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
670
685
|
/**
|
|
671
686
|
* Report that a same-pod TUI/shell child failed to spawn (fire-and-forget).
|
|
672
687
|
* The server Ends the orphaned session — no fallback pod (unlike review).
|
|
@@ -766,12 +781,13 @@ var AgentConnection = class _AgentConnection {
|
|
|
766
781
|
});
|
|
767
782
|
}
|
|
768
783
|
}
|
|
769
|
-
postChatMessage(content) {
|
|
784
|
+
postChatMessage(content, milestone) {
|
|
770
785
|
if (!this.socket) return;
|
|
771
786
|
if (this.suppressIfDuplicate(content)) return;
|
|
772
787
|
void this.call("postAgentMessage", {
|
|
773
788
|
sessionId: this.config.sessionId,
|
|
774
|
-
content
|
|
789
|
+
content,
|
|
790
|
+
milestone
|
|
775
791
|
}).catch(() => {
|
|
776
792
|
});
|
|
777
793
|
}
|
|
@@ -779,13 +795,14 @@ var AgentConnection = class _AgentConnection {
|
|
|
779
795
|
// the message is acknowledged by the server before proceeding (e.g. before
|
|
780
796
|
// aborting the session). Dedup still applies; a suppressed message resolves
|
|
781
797
|
// immediately without hitting the wire.
|
|
782
|
-
async postChatMessageAwait(content) {
|
|
798
|
+
async postChatMessageAwait(content, milestone) {
|
|
783
799
|
if (!this.socket) return;
|
|
784
800
|
if (this.suppressIfDuplicate(content)) return;
|
|
785
801
|
try {
|
|
786
802
|
await this.call("postAgentMessage", {
|
|
787
803
|
sessionId: this.config.sessionId,
|
|
788
|
-
content
|
|
804
|
+
content,
|
|
805
|
+
milestone
|
|
789
806
|
});
|
|
790
807
|
} catch (err) {
|
|
791
808
|
process.stderr.write(
|
|
@@ -990,6 +1007,12 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
990
1007
|
triggerIdentification() {
|
|
991
1008
|
return this.call("triggerIdentification", { sessionId: this.config.sessionId });
|
|
992
1009
|
}
|
|
1010
|
+
handoffToImplementer(payload) {
|
|
1011
|
+
return this.call("handoffToImplementer", {
|
|
1012
|
+
sessionId: this.config.sessionId,
|
|
1013
|
+
...payload
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
993
1016
|
async refreshAuthToken() {
|
|
994
1017
|
const result = await this.refreshFromBootstrap();
|
|
995
1018
|
return result.refreshedClaude;
|
|
@@ -2108,7 +2131,8 @@ var CreatePRInputSchema = z3.object({
|
|
|
2108
2131
|
});
|
|
2109
2132
|
var PostToChatInputSchema = z3.object({
|
|
2110
2133
|
message: z3.string().min(1),
|
|
2111
|
-
type: z3.enum(["message", "question", "update"]).optional().default("message")
|
|
2134
|
+
type: z3.enum(["message", "question", "update"]).optional().default("message"),
|
|
2135
|
+
milestone: z3.enum(["plan_ready", "implementation_complete", "blocked"]).optional()
|
|
2112
2136
|
});
|
|
2113
2137
|
var GetTaskContextRequestSchema = z3.object({
|
|
2114
2138
|
sessionId: z3.string(),
|
|
@@ -2294,7 +2318,10 @@ var UpdateTaskPropertiesRequestSchema = z3.object({
|
|
|
2294
2318
|
tagIds: z3.array(z3.string()).optional(),
|
|
2295
2319
|
tagNames: z3.array(z3.string()).optional(),
|
|
2296
2320
|
githubPRUrl: z3.string().url().optional(),
|
|
2297
|
-
githubBranch: z3.string().optional()
|
|
2321
|
+
githubBranch: z3.string().optional(),
|
|
2322
|
+
// Canonical risk level, or null to clear — same semantics as the headless
|
|
2323
|
+
// update_task boundary (resolved to the project's Risk row in the handler).
|
|
2324
|
+
risk: riskLevelSchema.nullable().optional()
|
|
2298
2325
|
});
|
|
2299
2326
|
var ListIconsRequestSchema = z3.object({
|
|
2300
2327
|
sessionId: z3.string()
|
|
@@ -2343,6 +2370,15 @@ var VoteSuggestionRequestSchema = z3.object({
|
|
|
2343
2370
|
var TriggerIdentificationRequestSchema = z3.object({
|
|
2344
2371
|
sessionId: z3.string()
|
|
2345
2372
|
});
|
|
2373
|
+
var HandoffToImplementerRequestSchema = z3.object({
|
|
2374
|
+
sessionId: z3.string(),
|
|
2375
|
+
// Optional difficulty sizing — sets the task's story points before resolving
|
|
2376
|
+
// the matched implementer agent. Omit to hand off using the task's current
|
|
2377
|
+
// story points (or the project's default task agent when unsized).
|
|
2378
|
+
storyPoints: z3.number().int().positive().optional(),
|
|
2379
|
+
// Optional kickoff note posted to the task chat alongside the handoff notice.
|
|
2380
|
+
message: z3.string().optional()
|
|
2381
|
+
});
|
|
2346
2382
|
var SubmitCodeReviewResultRequestSchema = z3.object({
|
|
2347
2383
|
sessionId: z3.string(),
|
|
2348
2384
|
approved: z3.boolean(),
|
|
@@ -2556,6 +2592,10 @@ var ListAccessibleProjectsRequestSchema = z4.object({
|
|
|
2556
2592
|
var ListProjectTasksRequestSchema = z4.object({
|
|
2557
2593
|
projectId: z4.string(),
|
|
2558
2594
|
status: z4.string().optional(),
|
|
2595
|
+
// Card types to include. Omitted/empty → defaults to ["task"] in the handler
|
|
2596
|
+
// (mirrors searchProjectTasks) so listing doesn't surface incidents/suggestions
|
|
2597
|
+
// unless asked. Enum validation lives at the MCP tool layer.
|
|
2598
|
+
typeFilters: z4.array(z4.string()).optional(),
|
|
2559
2599
|
assigneeId: z4.string().optional(),
|
|
2560
2600
|
unassigned: z4.boolean().optional(),
|
|
2561
2601
|
// Scope to a sub-project board when provided. Unlike the board layer's `?? null`
|
|
@@ -2596,6 +2636,9 @@ var GetProjectSummaryRequestSchema = z4.object({
|
|
|
2596
2636
|
var GetProjectOnboardingStatusRequestSchema = z4.object({
|
|
2597
2637
|
projectId: z4.string()
|
|
2598
2638
|
});
|
|
2639
|
+
var GetProjectConnectUrlsRequestSchema = z4.object({
|
|
2640
|
+
projectId: z4.string()
|
|
2641
|
+
});
|
|
2599
2642
|
var CreateProjectTaskRequestSchema = z4.object({
|
|
2600
2643
|
projectId: z4.string(),
|
|
2601
2644
|
title: z4.string().min(1),
|
|
@@ -2610,7 +2653,13 @@ var UpdateProjectTaskRequestSchema = z4.object({
|
|
|
2610
2653
|
projectId: z4.string(),
|
|
2611
2654
|
taskId: z4.string(),
|
|
2612
2655
|
title: z4.string().optional(),
|
|
2656
|
+
description: z4.string().optional(),
|
|
2613
2657
|
plan: z4.string().optional(),
|
|
2658
|
+
// Enum validation lives at the MCP tool layer (mirrors createProjectTask);
|
|
2659
|
+
// the handler routes through the shared updateStatus core (InProgress
|
|
2660
|
+
// dependency check + cleanup/board/Slack side effects), not the stricter
|
|
2661
|
+
// card-type-validating path the Socket.IO updateTaskStatus mutation uses.
|
|
2662
|
+
status: z4.string().optional(),
|
|
2614
2663
|
// Canonical risk level, or null to clear. Resolved to the project's
|
|
2615
2664
|
// configured Risk row (by rank) in the handler.
|
|
2616
2665
|
risk: riskLevelSchema.nullable().optional(),
|
|
@@ -2620,9 +2669,9 @@ var UpdateProjectTaskRequestSchema = z4.object({
|
|
|
2620
2669
|
subProjectId: z4.string().nullable().optional(),
|
|
2621
2670
|
requestingUserId: z4.string().optional()
|
|
2622
2671
|
}).strict().refine(
|
|
2623
|
-
(v) => v.title !== void 0 || v.plan !== void 0 || v.risk !== void 0 || v.assignedUserId !== void 0 || v.subProjectId !== void 0,
|
|
2672
|
+
(v) => v.title !== void 0 || v.description !== void 0 || v.plan !== void 0 || v.status !== void 0 || v.risk !== void 0 || v.assignedUserId !== void 0 || v.subProjectId !== void 0,
|
|
2624
2673
|
{
|
|
2625
|
-
message: "update_task requires at least one field to change (title, plan, risk, assignedUserId, or subProjectId)"
|
|
2674
|
+
message: "update_task requires at least one field to change (title, description, plan, status, risk, assignedUserId, or subProjectId)"
|
|
2626
2675
|
}
|
|
2627
2676
|
);
|
|
2628
2677
|
var TransitionProjectTaskStatusRequestSchema = z4.object({
|
|
@@ -2728,6 +2777,14 @@ var ResumeAdhocSessionRequestSchema = z4.object({
|
|
|
2728
2777
|
workspaceId: z4.string(),
|
|
2729
2778
|
requestingUserId: z4.string().optional()
|
|
2730
2779
|
});
|
|
2780
|
+
var RefreshCodingAgentKeyUsageRequestSchema = z4.object({
|
|
2781
|
+
projectId: z4.string(),
|
|
2782
|
+
keyId: z4.string().optional(),
|
|
2783
|
+
requestingUserId: z4.string().optional()
|
|
2784
|
+
});
|
|
2785
|
+
var ListKeysToProbeRequestSchema = z4.object({
|
|
2786
|
+
sessionId: z4.string()
|
|
2787
|
+
});
|
|
2731
2788
|
var CreateProjectReleaseRequestSchema = z4.object({
|
|
2732
2789
|
projectId: z4.string(),
|
|
2733
2790
|
taskIds: z4.array(z4.string()).optional(),
|
|
@@ -2766,6 +2823,9 @@ var UpdateProjectSubtaskRequestSchema = z4.object({
|
|
|
2766
2823
|
ordinal: z4.number().int().nonnegative().optional(),
|
|
2767
2824
|
storyPointValue: z4.number().int().positive().optional(),
|
|
2768
2825
|
followParentStatus: z4.boolean().optional(),
|
|
2826
|
+
/** Replace-set of sibling subtask ids/slugs this subtask blocks on ([] clears).
|
|
2827
|
+
* Mirrors the in-pod updateSubtask semantics. */
|
|
2828
|
+
dependsOn: z4.array(z4.string().min(1)).max(32).optional(),
|
|
2769
2829
|
requestingUserId: z4.string().optional()
|
|
2770
2830
|
});
|
|
2771
2831
|
var DeleteProjectSubtaskRequestSchema = z4.object({
|
|
@@ -3341,6 +3401,7 @@ var ClaudeCodeHarness = class {
|
|
|
3341
3401
|
};
|
|
3342
3402
|
|
|
3343
3403
|
// src/harness/pty/session.ts
|
|
3404
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3344
3405
|
import { mkdtemp as mkdtemp2, mkdir as mkdir3, rm as rm5 } from "fs/promises";
|
|
3345
3406
|
import { tmpdir as tmpdir3 } from "os";
|
|
3346
3407
|
import { join as join4, dirname } from "path";
|
|
@@ -3739,11 +3800,131 @@ function matchUsageLimitBanner(text, now = Date.now()) {
|
|
|
3739
3800
|
};
|
|
3740
3801
|
}
|
|
3741
3802
|
|
|
3803
|
+
// src/harness/pty/pty-support.ts
|
|
3804
|
+
import { stat as stat2 } from "fs/promises";
|
|
3805
|
+
var MAX_DIAGNOSTIC_OUTPUT = 4e3;
|
|
3806
|
+
var MAX_BETWEEN_TURN_BUFFER = 500;
|
|
3807
|
+
var SUBMIT_SETTLE_MS = 300;
|
|
3808
|
+
var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
|
|
3809
|
+
var SUBMIT_NUDGE_MAX_PRESSES = 5;
|
|
3810
|
+
var SUBMIT_NUDGE_SLOW_INTERVAL_MS = 5e3;
|
|
3811
|
+
var SUBMIT_NUDGE_WINDOW_MS = 9e4;
|
|
3812
|
+
var PLAN_DIALOG_FIRST_PRESS_MS = 700;
|
|
3813
|
+
var PLAN_DIALOG_INTERVAL_MS = 1500;
|
|
3814
|
+
var PLAN_DIALOG_SLOW_INTERVAL_MS = 5e3;
|
|
3815
|
+
var PLAN_DIALOG_FAST_WINDOW_MS = 1e4;
|
|
3816
|
+
var PLAN_DIALOG_WINDOW_MS = 9e4;
|
|
3817
|
+
function envMs(name, fallback) {
|
|
3818
|
+
const raw = Number(process.env[name]);
|
|
3819
|
+
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
|
|
3820
|
+
}
|
|
3821
|
+
function resolveSubmitSettleMs() {
|
|
3822
|
+
return envMs("CONVEYOR_PTY_SUBMIT_SETTLE_MS", SUBMIT_SETTLE_MS);
|
|
3823
|
+
}
|
|
3824
|
+
function resolveSubmitNudgeTiming() {
|
|
3825
|
+
return {
|
|
3826
|
+
intervalMs: envMs("CONVEYOR_PTY_NUDGE_INTERVAL_MS", SUBMIT_NUDGE_INTERVAL_MS),
|
|
3827
|
+
slowIntervalMs: envMs("CONVEYOR_PTY_NUDGE_SLOW_INTERVAL_MS", SUBMIT_NUDGE_SLOW_INTERVAL_MS),
|
|
3828
|
+
maxPresses: SUBMIT_NUDGE_MAX_PRESSES,
|
|
3829
|
+
windowMs: envMs("CONVEYOR_PTY_NUDGE_WINDOW_MS", SUBMIT_NUDGE_WINDOW_MS)
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
function resolvePlanDialogTiming() {
|
|
3833
|
+
return {
|
|
3834
|
+
firstPressMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FIRST_PRESS_MS", PLAN_DIALOG_FIRST_PRESS_MS),
|
|
3835
|
+
intervalMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_INTERVAL_MS", PLAN_DIALOG_INTERVAL_MS),
|
|
3836
|
+
slowIntervalMs: envMs(
|
|
3837
|
+
"CONVEYOR_PTY_PLAN_DIALOG_SLOW_INTERVAL_MS",
|
|
3838
|
+
PLAN_DIALOG_SLOW_INTERVAL_MS
|
|
3839
|
+
),
|
|
3840
|
+
fastWindowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FAST_WINDOW_MS", PLAN_DIALOG_FAST_WINDOW_MS),
|
|
3841
|
+
windowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_WINDOW_MS", PLAN_DIALOG_WINDOW_MS)
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
function turnOptionsFrom(options) {
|
|
3845
|
+
return {
|
|
3846
|
+
canUseTool: options.canUseTool,
|
|
3847
|
+
promptDelivery: options.promptDelivery,
|
|
3848
|
+
planDialogAutoAccept: options.planDialogAutoAccept,
|
|
3849
|
+
abortController: options.abortController
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
function isRecord2(value) {
|
|
3853
|
+
return typeof value === "object" && value !== null;
|
|
3854
|
+
}
|
|
3855
|
+
function extractSpawn(mod) {
|
|
3856
|
+
if (!isRecord2(mod)) return null;
|
|
3857
|
+
if (typeof mod.spawn === "function") return mod.spawn;
|
|
3858
|
+
const def = mod.default;
|
|
3859
|
+
if (isRecord2(def) && typeof def.spawn === "function") return def.spawn;
|
|
3860
|
+
return null;
|
|
3861
|
+
}
|
|
3862
|
+
async function loadPtySpawn() {
|
|
3863
|
+
const mod = await import("node-pty");
|
|
3864
|
+
const spawn2 = extractSpawn(mod);
|
|
3865
|
+
if (!spawn2) throw new Error("node-pty: spawn export not found");
|
|
3866
|
+
return spawn2;
|
|
3867
|
+
}
|
|
3868
|
+
function inheritedEnv(socketPath) {
|
|
3869
|
+
const env = {};
|
|
3870
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
3871
|
+
if (typeof value === "string") env[key] = value;
|
|
3872
|
+
}
|
|
3873
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
3874
|
+
delete env.ANTHROPIC_API_KEY;
|
|
3875
|
+
}
|
|
3876
|
+
if (socketPath) {
|
|
3877
|
+
env.CONVEYOR_HOOK_SOCKET = socketPath;
|
|
3878
|
+
}
|
|
3879
|
+
env.MCP_TIMEOUT ??= "60000";
|
|
3880
|
+
env.MCP_TOOL_TIMEOUT ??= "180000";
|
|
3881
|
+
return env;
|
|
3882
|
+
}
|
|
3883
|
+
function buildPromptBytes(text) {
|
|
3884
|
+
return `\x1B[200~${text}\x1B[201~`;
|
|
3885
|
+
}
|
|
3886
|
+
function renderPromptContentText(content) {
|
|
3887
|
+
return content.map((block) => {
|
|
3888
|
+
const b = block;
|
|
3889
|
+
if (b?.type === "text" && typeof b.text === "string") return b.text;
|
|
3890
|
+
if (b?.type === "image") {
|
|
3891
|
+
return `[Image attachment \u2014 use list_task_files / get_attachment to view]`;
|
|
3892
|
+
}
|
|
3893
|
+
return JSON.stringify(block);
|
|
3894
|
+
}).join("\n\n");
|
|
3895
|
+
}
|
|
3896
|
+
async function transcriptSize(path4) {
|
|
3897
|
+
try {
|
|
3898
|
+
return (await stat2(path4)).size;
|
|
3899
|
+
} catch {
|
|
3900
|
+
return 0;
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
function parseUserQuestions(input) {
|
|
3904
|
+
if (!Array.isArray(input.questions)) return [];
|
|
3905
|
+
const questions = [];
|
|
3906
|
+
for (const entry of input.questions) {
|
|
3907
|
+
if (!isRecord2(entry)) continue;
|
|
3908
|
+
if (typeof entry.question !== "string") continue;
|
|
3909
|
+
const options = Array.isArray(entry.options) ? entry.options.filter(isRecord2).filter((o) => typeof o.label === "string").map((o) => ({
|
|
3910
|
+
label: o.label,
|
|
3911
|
+
description: typeof o.description === "string" ? o.description : ""
|
|
3912
|
+
})) : [];
|
|
3913
|
+
questions.push({
|
|
3914
|
+
question: entry.question,
|
|
3915
|
+
header: typeof entry.header === "string" ? entry.header : "",
|
|
3916
|
+
options,
|
|
3917
|
+
...typeof entry.multiSelect === "boolean" ? { multiSelect: entry.multiSelect } : {}
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
return questions;
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3742
3923
|
// src/harness/pty/chat-record-mapper.ts
|
|
3743
3924
|
var TEXT_MAX = 16e3;
|
|
3744
3925
|
var TOOL_INPUT_MAX = 1900;
|
|
3745
3926
|
var TOOL_OUTPUT_MAX = 1900;
|
|
3746
|
-
function
|
|
3927
|
+
function isRecord3(value) {
|
|
3747
3928
|
return typeof value === "object" && value !== null;
|
|
3748
3929
|
}
|
|
3749
3930
|
function isUnknownArray2(value) {
|
|
@@ -3759,6 +3940,27 @@ function stringField2(record, ...keys) {
|
|
|
3759
3940
|
function truncate(text, max) {
|
|
3760
3941
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
3761
3942
|
}
|
|
3943
|
+
function compactQuestionsJson(questions) {
|
|
3944
|
+
const serialize = (qs) => JSON.stringify({ questions: qs });
|
|
3945
|
+
const withDescriptions = (max) => questions.map((q) => ({
|
|
3946
|
+
...q,
|
|
3947
|
+
options: q.options.map((o) => ({ ...o, description: truncate(o.description, max) }))
|
|
3948
|
+
}));
|
|
3949
|
+
const full = serialize(questions);
|
|
3950
|
+
if (full.length <= TOOL_INPUT_MAX) return full;
|
|
3951
|
+
const shortened = serialize(withDescriptions(80));
|
|
3952
|
+
if (shortened.length <= TOOL_INPUT_MAX) return shortened;
|
|
3953
|
+
const bare = serialize(withDescriptions(0));
|
|
3954
|
+
if (bare.length <= TOOL_INPUT_MAX) return bare;
|
|
3955
|
+
return bare.slice(0, TOOL_INPUT_MAX);
|
|
3956
|
+
}
|
|
3957
|
+
function compactToolInput(name, input) {
|
|
3958
|
+
if (name === "AskUserQuestion" && isRecord3(input)) {
|
|
3959
|
+
const questions = parseUserQuestions(input);
|
|
3960
|
+
if (questions.length > 0) return compactQuestionsJson(questions);
|
|
3961
|
+
}
|
|
3962
|
+
return JSON.stringify(input ?? {}).slice(0, TOOL_INPUT_MAX);
|
|
3963
|
+
}
|
|
3762
3964
|
function isNonConversationText(text) {
|
|
3763
3965
|
const trimmed = text.trimStart();
|
|
3764
3966
|
return trimmed.startsWith("<command-name>") || trimmed.startsWith("<local-command-") || trimmed.startsWith("<task-notification>");
|
|
@@ -3778,11 +3980,11 @@ function mapSystem2(record) {
|
|
|
3778
3980
|
}
|
|
3779
3981
|
function mapAssistant2(record) {
|
|
3780
3982
|
const message = record.message;
|
|
3781
|
-
if (!
|
|
3983
|
+
if (!isRecord3(message)) return [];
|
|
3782
3984
|
const content = isUnknownArray2(message.content) ? message.content : [];
|
|
3783
3985
|
const events = [];
|
|
3784
3986
|
for (const raw of content) {
|
|
3785
|
-
if (!
|
|
3987
|
+
if (!isRecord3(raw)) continue;
|
|
3786
3988
|
if (raw.type === "text") {
|
|
3787
3989
|
const text = stringField2(raw, "text");
|
|
3788
3990
|
if (text && text.length > 0) {
|
|
@@ -3795,7 +3997,7 @@ function mapAssistant2(record) {
|
|
|
3795
3997
|
const event = {
|
|
3796
3998
|
kind: "tool_use",
|
|
3797
3999
|
name: truncate(name, 200),
|
|
3798
|
-
input:
|
|
4000
|
+
input: compactToolInput(name, input)
|
|
3799
4001
|
};
|
|
3800
4002
|
const id = stringField2(raw, "id");
|
|
3801
4003
|
if (id !== void 0) event.id = id;
|
|
@@ -3809,14 +4011,14 @@ function toolResultText(block) {
|
|
|
3809
4011
|
const content = block.content;
|
|
3810
4012
|
if (typeof content === "string") return content;
|
|
3811
4013
|
if (isUnknownArray2(content)) {
|
|
3812
|
-
return content.filter((b) =>
|
|
4014
|
+
return content.filter((b) => isRecord3(b) && b.type === "text").map((b) => typeof b.text === "string" ? b.text : "").filter((t) => t.length > 0).join("\n");
|
|
3813
4015
|
}
|
|
3814
4016
|
return "";
|
|
3815
4017
|
}
|
|
3816
4018
|
function mapToolResults(content) {
|
|
3817
4019
|
const events = [];
|
|
3818
4020
|
for (const raw of content) {
|
|
3819
|
-
if (!
|
|
4021
|
+
if (!isRecord3(raw) || raw.type !== "tool_result") continue;
|
|
3820
4022
|
const event = {
|
|
3821
4023
|
kind: "tool_result",
|
|
3822
4024
|
output: truncate(toolResultText(raw), TOOL_OUTPUT_MAX),
|
|
@@ -3830,15 +4032,15 @@ function mapToolResults(content) {
|
|
|
3830
4032
|
}
|
|
3831
4033
|
function mapUser(record) {
|
|
3832
4034
|
const message = record.message;
|
|
3833
|
-
if (!
|
|
4035
|
+
if (!isRecord3(message)) return [];
|
|
3834
4036
|
const content = message.content;
|
|
3835
4037
|
let text;
|
|
3836
4038
|
if (typeof content === "string") {
|
|
3837
4039
|
text = content;
|
|
3838
4040
|
} else if (isUnknownArray2(content)) {
|
|
3839
|
-
const hasToolResult = content.some((b) =>
|
|
4041
|
+
const hasToolResult = content.some((b) => isRecord3(b) && b.type === "tool_result");
|
|
3840
4042
|
if (hasToolResult) return mapToolResults(content);
|
|
3841
|
-
text = content.filter((b) =>
|
|
4043
|
+
text = content.filter((b) => isRecord3(b) && b.type === "text").map((b) => typeof b.text === "string" ? b.text : "").filter((t) => t.length > 0).join("\n");
|
|
3842
4044
|
} else {
|
|
3843
4045
|
return [];
|
|
3844
4046
|
}
|
|
@@ -3847,7 +4049,7 @@ function mapUser(record) {
|
|
|
3847
4049
|
return [{ kind: "user_text", text: truncate(trimmed, TEXT_MAX) }];
|
|
3848
4050
|
}
|
|
3849
4051
|
function mapChatRecords(raw) {
|
|
3850
|
-
if (!
|
|
4052
|
+
if (!isRecord3(raw)) return [];
|
|
3851
4053
|
if (raw.isSidechain === true || raw.isMeta === true) return [];
|
|
3852
4054
|
switch (raw.type) {
|
|
3853
4055
|
case "system":
|
|
@@ -4751,126 +4953,6 @@ async function removeConveyorCredentials(env = process.env) {
|
|
|
4751
4953
|
}
|
|
4752
4954
|
}
|
|
4753
4955
|
|
|
4754
|
-
// src/harness/pty/pty-support.ts
|
|
4755
|
-
import { stat as stat2 } from "fs/promises";
|
|
4756
|
-
var MAX_DIAGNOSTIC_OUTPUT = 4e3;
|
|
4757
|
-
var MAX_BETWEEN_TURN_BUFFER = 500;
|
|
4758
|
-
var SUBMIT_SETTLE_MS = 300;
|
|
4759
|
-
var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
|
|
4760
|
-
var SUBMIT_NUDGE_MAX_PRESSES = 5;
|
|
4761
|
-
var SUBMIT_NUDGE_SLOW_INTERVAL_MS = 5e3;
|
|
4762
|
-
var SUBMIT_NUDGE_WINDOW_MS = 9e4;
|
|
4763
|
-
var PLAN_DIALOG_FIRST_PRESS_MS = 700;
|
|
4764
|
-
var PLAN_DIALOG_INTERVAL_MS = 1500;
|
|
4765
|
-
var PLAN_DIALOG_SLOW_INTERVAL_MS = 5e3;
|
|
4766
|
-
var PLAN_DIALOG_FAST_WINDOW_MS = 1e4;
|
|
4767
|
-
var PLAN_DIALOG_WINDOW_MS = 9e4;
|
|
4768
|
-
function envMs(name, fallback) {
|
|
4769
|
-
const raw = Number(process.env[name]);
|
|
4770
|
-
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
|
|
4771
|
-
}
|
|
4772
|
-
function resolveSubmitSettleMs() {
|
|
4773
|
-
return envMs("CONVEYOR_PTY_SUBMIT_SETTLE_MS", SUBMIT_SETTLE_MS);
|
|
4774
|
-
}
|
|
4775
|
-
function resolveSubmitNudgeTiming() {
|
|
4776
|
-
return {
|
|
4777
|
-
intervalMs: envMs("CONVEYOR_PTY_NUDGE_INTERVAL_MS", SUBMIT_NUDGE_INTERVAL_MS),
|
|
4778
|
-
slowIntervalMs: envMs("CONVEYOR_PTY_NUDGE_SLOW_INTERVAL_MS", SUBMIT_NUDGE_SLOW_INTERVAL_MS),
|
|
4779
|
-
maxPresses: SUBMIT_NUDGE_MAX_PRESSES,
|
|
4780
|
-
windowMs: envMs("CONVEYOR_PTY_NUDGE_WINDOW_MS", SUBMIT_NUDGE_WINDOW_MS)
|
|
4781
|
-
};
|
|
4782
|
-
}
|
|
4783
|
-
function resolvePlanDialogTiming() {
|
|
4784
|
-
return {
|
|
4785
|
-
firstPressMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FIRST_PRESS_MS", PLAN_DIALOG_FIRST_PRESS_MS),
|
|
4786
|
-
intervalMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_INTERVAL_MS", PLAN_DIALOG_INTERVAL_MS),
|
|
4787
|
-
slowIntervalMs: envMs(
|
|
4788
|
-
"CONVEYOR_PTY_PLAN_DIALOG_SLOW_INTERVAL_MS",
|
|
4789
|
-
PLAN_DIALOG_SLOW_INTERVAL_MS
|
|
4790
|
-
),
|
|
4791
|
-
fastWindowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FAST_WINDOW_MS", PLAN_DIALOG_FAST_WINDOW_MS),
|
|
4792
|
-
windowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_WINDOW_MS", PLAN_DIALOG_WINDOW_MS)
|
|
4793
|
-
};
|
|
4794
|
-
}
|
|
4795
|
-
function turnOptionsFrom(options) {
|
|
4796
|
-
return {
|
|
4797
|
-
canUseTool: options.canUseTool,
|
|
4798
|
-
promptDelivery: options.promptDelivery,
|
|
4799
|
-
planDialogAutoAccept: options.planDialogAutoAccept,
|
|
4800
|
-
abortController: options.abortController
|
|
4801
|
-
};
|
|
4802
|
-
}
|
|
4803
|
-
function isRecord3(value) {
|
|
4804
|
-
return typeof value === "object" && value !== null;
|
|
4805
|
-
}
|
|
4806
|
-
function extractSpawn(mod) {
|
|
4807
|
-
if (!isRecord3(mod)) return null;
|
|
4808
|
-
if (typeof mod.spawn === "function") return mod.spawn;
|
|
4809
|
-
const def = mod.default;
|
|
4810
|
-
if (isRecord3(def) && typeof def.spawn === "function") return def.spawn;
|
|
4811
|
-
return null;
|
|
4812
|
-
}
|
|
4813
|
-
async function loadPtySpawn() {
|
|
4814
|
-
const mod = await import("node-pty");
|
|
4815
|
-
const spawn2 = extractSpawn(mod);
|
|
4816
|
-
if (!spawn2) throw new Error("node-pty: spawn export not found");
|
|
4817
|
-
return spawn2;
|
|
4818
|
-
}
|
|
4819
|
-
function inheritedEnv(socketPath) {
|
|
4820
|
-
const env = {};
|
|
4821
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
4822
|
-
if (typeof value === "string") env[key] = value;
|
|
4823
|
-
}
|
|
4824
|
-
if (env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
4825
|
-
delete env.ANTHROPIC_API_KEY;
|
|
4826
|
-
}
|
|
4827
|
-
if (socketPath) {
|
|
4828
|
-
env.CONVEYOR_HOOK_SOCKET = socketPath;
|
|
4829
|
-
}
|
|
4830
|
-
env.MCP_TIMEOUT ??= "60000";
|
|
4831
|
-
env.MCP_TOOL_TIMEOUT ??= "180000";
|
|
4832
|
-
return env;
|
|
4833
|
-
}
|
|
4834
|
-
function buildPromptBytes(text) {
|
|
4835
|
-
return `\x1B[200~${text}\x1B[201~`;
|
|
4836
|
-
}
|
|
4837
|
-
function renderPromptContentText(content) {
|
|
4838
|
-
return content.map((block) => {
|
|
4839
|
-
const b = block;
|
|
4840
|
-
if (b?.type === "text" && typeof b.text === "string") return b.text;
|
|
4841
|
-
if (b?.type === "image") {
|
|
4842
|
-
return `[Image attachment \u2014 use list_task_files / get_attachment to view]`;
|
|
4843
|
-
}
|
|
4844
|
-
return JSON.stringify(block);
|
|
4845
|
-
}).join("\n\n");
|
|
4846
|
-
}
|
|
4847
|
-
async function transcriptSize(path4) {
|
|
4848
|
-
try {
|
|
4849
|
-
return (await stat2(path4)).size;
|
|
4850
|
-
} catch {
|
|
4851
|
-
return 0;
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
function parseUserQuestions(input) {
|
|
4855
|
-
if (!Array.isArray(input.questions)) return [];
|
|
4856
|
-
const questions = [];
|
|
4857
|
-
for (const entry of input.questions) {
|
|
4858
|
-
if (!isRecord3(entry)) continue;
|
|
4859
|
-
if (typeof entry.question !== "string") continue;
|
|
4860
|
-
const options = Array.isArray(entry.options) ? entry.options.filter(isRecord3).filter((o) => typeof o.label === "string").map((o) => ({
|
|
4861
|
-
label: o.label,
|
|
4862
|
-
description: typeof o.description === "string" ? o.description : ""
|
|
4863
|
-
})) : [];
|
|
4864
|
-
questions.push({
|
|
4865
|
-
question: entry.question,
|
|
4866
|
-
header: typeof entry.header === "string" ? entry.header : "",
|
|
4867
|
-
options,
|
|
4868
|
-
...typeof entry.multiSelect === "boolean" ? { multiSelect: entry.multiSelect } : {}
|
|
4869
|
-
});
|
|
4870
|
-
}
|
|
4871
|
-
return questions;
|
|
4872
|
-
}
|
|
4873
|
-
|
|
4874
4956
|
// src/harness/pty/adapters/claude.ts
|
|
4875
4957
|
var ClaudeTuiAdapter = class {
|
|
4876
4958
|
id = "claude-code";
|
|
@@ -4981,6 +5063,18 @@ var PtySession = class {
|
|
|
4981
5063
|
// Submit-nudge state (see armSubmitNudge).
|
|
4982
5064
|
pendingSubmitNudge = false;
|
|
4983
5065
|
submitNudgeTimer = null;
|
|
5066
|
+
// Synthetic AskUserQuestion chat-card state. The CLI does NOT flush the
|
|
5067
|
+
// assistant record holding a pending AskUserQuestion tool_use to the
|
|
5068
|
+
// transcript until the questionnaire resolves (verified live on CLI 2.1.209:
|
|
5069
|
+
// dialog parked on screen, transcript untouched) — so a transcript-derived
|
|
5070
|
+
// question card could only ever render AFTER the human answered in the raw
|
|
5071
|
+
// terminal. Instead the PreToolUse hook (which fires at ask time and carries
|
|
5072
|
+
// the full questions input) emits a synthetic `tool_use` chat event under an
|
|
5073
|
+
// `aq-…` id. When the real records eventually flush, the duplicate tool_use
|
|
5074
|
+
// is dropped (FIFO match below) and its tool_result is re-pointed at the
|
|
5075
|
+
// synthetic id so the card flips to answered.
|
|
5076
|
+
pendingSyntheticQuestionIds = [];
|
|
5077
|
+
questionResultRemap = /* @__PURE__ */ new Map();
|
|
4984
5078
|
// Per-turn state: the prompt to feed and the per-turn options subset. Both
|
|
4985
5079
|
// start from the constructor args (turn 1) and are replaced by beginTurn.
|
|
4986
5080
|
turnPrompt;
|
|
@@ -5102,6 +5196,7 @@ var PtySession = class {
|
|
|
5102
5196
|
this.passiveSignaled = false;
|
|
5103
5197
|
this.disarmSubmitNudge();
|
|
5104
5198
|
this.disarmPlanDialogAutoAccept();
|
|
5199
|
+
this.closeSyntheticQuestionCards();
|
|
5105
5200
|
}
|
|
5106
5201
|
/**
|
|
5107
5202
|
* (Re)register the abort→teardown listener on the current turn's controller,
|
|
@@ -5133,6 +5228,7 @@ var PtySession = class {
|
|
|
5133
5228
|
*/
|
|
5134
5229
|
endTurn(clean) {
|
|
5135
5230
|
this.lastTurnCleanResult = clean;
|
|
5231
|
+
this.closeSyntheticQuestionCards();
|
|
5136
5232
|
this.activeQueue?.close();
|
|
5137
5233
|
this.activeQueue = null;
|
|
5138
5234
|
if (this.abortHandler && this.turn.abortController) {
|
|
@@ -5207,17 +5303,72 @@ var PtySession = class {
|
|
|
5207
5303
|
const transcriptPath = sessionTranscriptPath(this.options.cwd, sessionId);
|
|
5208
5304
|
await mkdir3(dirname(transcriptPath), { recursive: true });
|
|
5209
5305
|
const startOffset = this.resume ? await transcriptSize(transcriptPath) : 0;
|
|
5210
|
-
const sendChat = this.bridge?.sendChatEvent?.bind(this.bridge);
|
|
5211
5306
|
this.tailer = new JsonlTailer(
|
|
5212
5307
|
transcriptPath,
|
|
5213
5308
|
(event) => this.handleTranscriptEvent(event),
|
|
5214
|
-
|
|
5215
|
-
for (const chatEvent of mapChatRecords(raw)) sendChat(chatEvent);
|
|
5216
|
-
} : void 0
|
|
5309
|
+
typeof this.bridge?.sendChatEvent === "function" ? (raw) => this.relayChatRecord(raw) : void 0
|
|
5217
5310
|
);
|
|
5218
5311
|
this.tailer.start(startOffset);
|
|
5219
5312
|
return { settingsPath, socketPath };
|
|
5220
5313
|
}
|
|
5314
|
+
/**
|
|
5315
|
+
* Project a tailed transcript record to chat events, reconciling them with
|
|
5316
|
+
* any synthetic question card already emitted at hook time: the flushed
|
|
5317
|
+
* AskUserQuestion `tool_use` duplicate is dropped (its real id remembered),
|
|
5318
|
+
* and the paired `tool_result` is re-pointed at the synthetic id so the
|
|
5319
|
+
* live-rendered card is the one that flips to answered.
|
|
5320
|
+
*/
|
|
5321
|
+
relayChatRecord(raw) {
|
|
5322
|
+
for (const event of mapChatRecords(raw)) {
|
|
5323
|
+
if (event.kind === "tool_use" && event.name === "AskUserQuestion") {
|
|
5324
|
+
const syntheticId = this.pendingSyntheticQuestionIds.shift();
|
|
5325
|
+
if (syntheticId) {
|
|
5326
|
+
if (event.id) this.questionResultRemap.set(event.id, syntheticId);
|
|
5327
|
+
continue;
|
|
5328
|
+
}
|
|
5329
|
+
} else if (event.kind === "tool_result" && event.toolUseId) {
|
|
5330
|
+
const syntheticId = this.questionResultRemap.get(event.toolUseId);
|
|
5331
|
+
if (syntheticId) {
|
|
5332
|
+
this.questionResultRemap.delete(event.toolUseId);
|
|
5333
|
+
this.sendChatEvent({ ...event, toolUseId: syntheticId });
|
|
5334
|
+
continue;
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
this.sendChatEvent(event);
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
sendChatEvent(event) {
|
|
5341
|
+
this.bridge?.sendChatEvent?.(event);
|
|
5342
|
+
}
|
|
5343
|
+
/** Render the question card in the web chat NOW — at hook time — instead of
|
|
5344
|
+
* whenever the CLI flushes the transcript records (which is only after the
|
|
5345
|
+
* questionnaire resolves; see the field comment). */
|
|
5346
|
+
emitSyntheticQuestionCard(questions) {
|
|
5347
|
+
if (questions.length === 0 || typeof this.bridge?.sendChatEvent !== "function") return;
|
|
5348
|
+
const id = `aq-${randomUUID2()}`;
|
|
5349
|
+
this.pendingSyntheticQuestionIds.push(id);
|
|
5350
|
+
this.sendChatEvent({
|
|
5351
|
+
kind: "tool_use",
|
|
5352
|
+
name: "AskUserQuestion",
|
|
5353
|
+
input: compactQuestionsJson(questions),
|
|
5354
|
+
id
|
|
5355
|
+
});
|
|
5356
|
+
}
|
|
5357
|
+
/**
|
|
5358
|
+
* Close any still-open synthetic question cards. The questionnaire can only
|
|
5359
|
+
* outlive its card via a path that never flushes the paired records — Esc /
|
|
5360
|
+
* interrupt, a superseding turn, or process teardown — so an answering
|
|
5361
|
+
* tool_result will never arrive for these ids; emit one so the web card
|
|
5362
|
+
* stops soliciting input for a dialog that no longer exists.
|
|
5363
|
+
*/
|
|
5364
|
+
closeSyntheticQuestionCards() {
|
|
5365
|
+
const orphaned = [...this.pendingSyntheticQuestionIds, ...this.questionResultRemap.values()];
|
|
5366
|
+
this.pendingSyntheticQuestionIds = [];
|
|
5367
|
+
this.questionResultRemap.clear();
|
|
5368
|
+
for (const toolUseId of orphaned) {
|
|
5369
|
+
this.sendChatEvent({ kind: "tool_result", toolUseId, output: "", isError: false });
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5221
5372
|
writeStdin(text) {
|
|
5222
5373
|
this.pty?.write(text);
|
|
5223
5374
|
}
|
|
@@ -5251,6 +5402,7 @@ var PtySession = class {
|
|
|
5251
5402
|
this._toreDown = true;
|
|
5252
5403
|
this.disarmPlanDialogAutoAccept();
|
|
5253
5404
|
this.disarmSubmitNudge();
|
|
5405
|
+
this.closeSyntheticQuestionCards();
|
|
5254
5406
|
this.unsubInput?.();
|
|
5255
5407
|
this.unsubInput = null;
|
|
5256
5408
|
this.unsubResize?.();
|
|
@@ -5421,10 +5573,9 @@ var PtySession = class {
|
|
|
5421
5573
|
if (request.tool_name === "AskUserQuestion") {
|
|
5422
5574
|
this.disarmSubmitNudge();
|
|
5423
5575
|
this.disarmPlanDialogAutoAccept();
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
});
|
|
5576
|
+
const questions = parseUserQuestions(request.tool_input);
|
|
5577
|
+
this.pushEvent({ type: "user_question", questions });
|
|
5578
|
+
this.emitSyntheticQuestionCard(questions);
|
|
5428
5579
|
return { decision: "allow" };
|
|
5429
5580
|
}
|
|
5430
5581
|
const canUseTool = this.turn.canUseTool;
|
|
@@ -7493,9 +7644,12 @@ function buildPostToChatTool(connection) {
|
|
|
7493
7644
|
"Post a message to the task chat for the team to see. Your turn output is NOT shown in chat, so this is the only way the team sees your status, summaries, and questions. Omit task_id to post to the current task's chat; pass a child's ID to message its chat.",
|
|
7494
7645
|
{
|
|
7495
7646
|
message: z9.string().describe("The message to post to the team"),
|
|
7496
|
-
task_id: z9.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
|
|
7647
|
+
task_id: z9.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat."),
|
|
7648
|
+
milestone: z9.enum(["plan_ready", "implementation_complete", "blocked"]).optional().describe(
|
|
7649
|
+
"Declare a narrative milestone instead of a routine update. Use SPARINGLY \u2014 only when the plan is ready, implementation is complete, or you are blocked. Milestones appear on the card's activity timeline and in Slack."
|
|
7650
|
+
)
|
|
7497
7651
|
},
|
|
7498
|
-
async ({ message, task_id }) => {
|
|
7652
|
+
async ({ message, task_id, milestone }) => {
|
|
7499
7653
|
try {
|
|
7500
7654
|
if (task_id) {
|
|
7501
7655
|
await connection.call("postChildChatMessage", {
|
|
@@ -7516,7 +7670,7 @@ function buildPostToChatTool(connection) {
|
|
|
7516
7670
|
})
|
|
7517
7671
|
);
|
|
7518
7672
|
}
|
|
7519
|
-
await connection.call("postToChat", { message });
|
|
7673
|
+
await connection.call("postToChat", { message, milestone });
|
|
7520
7674
|
return textResult(JSON.stringify({ posted: true }));
|
|
7521
7675
|
} catch (error) {
|
|
7522
7676
|
return textResult(
|
|
@@ -7531,7 +7685,16 @@ function buildForceUpdateTaskStatusTool(connection) {
|
|
|
7531
7685
|
"force_update_task_status",
|
|
7532
7686
|
"EMERGENCY ONLY: force-override a task's Kanban status. Use when an automatic transition failed and the task is wedged. Normal flow transitions status automatically.",
|
|
7533
7687
|
{
|
|
7534
|
-
status: z9.enum([
|
|
7688
|
+
status: z9.enum([
|
|
7689
|
+
"Planning",
|
|
7690
|
+
"Open",
|
|
7691
|
+
"InProgress",
|
|
7692
|
+
"ReviewPR",
|
|
7693
|
+
"ReviewDev",
|
|
7694
|
+
"ReviewLive",
|
|
7695
|
+
"Complete",
|
|
7696
|
+
"Cancelled"
|
|
7697
|
+
]).describe("The new status for the task"),
|
|
7535
7698
|
task_id: z9.string().optional().describe("Child task ID to update. Omit to update the current task.")
|
|
7536
7699
|
},
|
|
7537
7700
|
async ({ status, task_id }) => {
|
|
@@ -8096,6 +8259,36 @@ function buildUpdateTaskTool(connection) {
|
|
|
8096
8259
|
}
|
|
8097
8260
|
);
|
|
8098
8261
|
}
|
|
8262
|
+
function buildHandoffTool(connection) {
|
|
8263
|
+
return defineTool(
|
|
8264
|
+
"handoff_to_agent",
|
|
8265
|
+
"Hand this task off to an implementer agent for the build phase \u2014 mid-conversation, same session, no restart. Call this once the plan is compiled and saved (update_task_plan). The server swaps this task to the difficulty-sized implementer agent (which may run at a different model level), announces the handoff in the activity log + chat, and switches you into build mode to start implementing. Size the work with the storyPoints arg (or set it first via update_task_properties). Returns the implementer's name + model.",
|
|
8266
|
+
{
|
|
8267
|
+
storyPoints: z12.number().int().positive().optional().describe(
|
|
8268
|
+
"Difficulty sizing (1=Common, 2=Magic, 3=Rare, 5=Unique, 8=Pack) \u2014 picks which implementer agent takes over. Omit to use the task's current story points."
|
|
8269
|
+
),
|
|
8270
|
+
message: z12.string().optional().describe("Optional kickoff note posted to the chat alongside the handoff notice.")
|
|
8271
|
+
},
|
|
8272
|
+
async ({ storyPoints, message }) => {
|
|
8273
|
+
try {
|
|
8274
|
+
const result = await connection.handoffToImplementer({
|
|
8275
|
+
...storyPoints !== void 0 && { storyPoints },
|
|
8276
|
+
...message !== void 0 && { message }
|
|
8277
|
+
});
|
|
8278
|
+
if (!result.handedOff) {
|
|
8279
|
+
return textResult(`Handoff did not complete: ${result.reason ?? "unknown reason"}`);
|
|
8280
|
+
}
|
|
8281
|
+
return textResult(
|
|
8282
|
+
`Handed off to ${result.agentName} (${result.model}). Now in build mode \u2014 start implementing the plan.`
|
|
8283
|
+
);
|
|
8284
|
+
} catch (error) {
|
|
8285
|
+
return textResult(
|
|
8286
|
+
`Failed to hand off: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
8287
|
+
);
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
);
|
|
8291
|
+
}
|
|
8099
8292
|
function buildCreateSubtaskTool(connection) {
|
|
8100
8293
|
return defineTool(
|
|
8101
8294
|
"create_subtask",
|
|
@@ -8318,42 +8511,52 @@ function buildPmTools(connection, options) {
|
|
|
8318
8511
|
// src/tools/discovery-tools.ts
|
|
8319
8512
|
import { z as z13 } from "zod";
|
|
8320
8513
|
var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique). The key is 'storyPointValue' \u2014 not 'storyPoints'.";
|
|
8321
|
-
var VALID_PROPERTY_KEYS = "title, storyPointValue, tagNames, githubPRUrl, githubBranch";
|
|
8514
|
+
var VALID_PROPERTY_KEYS = "title, storyPointValue, tagNames, githubPRUrl, githubBranch, risk";
|
|
8515
|
+
function describeUpdatedFields(p) {
|
|
8516
|
+
const fields = [];
|
|
8517
|
+
if (p.title !== void 0) fields.push(`title to "${p.title}"`);
|
|
8518
|
+
if (p.storyPointValue !== void 0) fields.push(`story points to ${p.storyPointValue}`);
|
|
8519
|
+
if (p.tagNames !== void 0) fields.push(`tags (${p.tagNames.length} tag(s))`);
|
|
8520
|
+
if (p.githubPRUrl !== void 0) fields.push(`PR link to "${p.githubPRUrl}"`);
|
|
8521
|
+
if (p.githubBranch !== void 0) fields.push(`branch to "${p.githubBranch}"`);
|
|
8522
|
+
if (p.risk !== void 0) fields.push(`risk to ${p.risk ?? "cleared"}`);
|
|
8523
|
+
return fields;
|
|
8524
|
+
}
|
|
8322
8525
|
function buildDiscoveryTools(connection) {
|
|
8323
8526
|
return [
|
|
8324
8527
|
defineTool(
|
|
8325
8528
|
"update_task_properties",
|
|
8326
|
-
"Set one or more task properties in a single call. Valid keys: title, storyPointValue, tagNames, githubPRUrl, githubBranch. All are optional \u2014 include only the ones you want to update (at least one). Unknown keys are rejected.",
|
|
8529
|
+
"Set one or more task properties in a single call. Valid keys: title, storyPointValue, tagNames, githubPRUrl, githubBranch, risk. All are optional \u2014 include only the ones you want to update (at least one). Unknown keys are rejected.",
|
|
8327
8530
|
{
|
|
8328
8531
|
title: z13.string().optional().describe("The new task title"),
|
|
8329
8532
|
storyPointValue: z13.number().optional().describe(SP_DESCRIPTION2),
|
|
8330
8533
|
tagNames: z13.array(z13.string()).optional().describe("Array of tag names to assign"),
|
|
8331
8534
|
githubPRUrl: z13.string().url().optional().describe("GitHub pull request URL to link to this task"),
|
|
8332
|
-
githubBranch: z13.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
|
|
8535
|
+
githubBranch: z13.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')"),
|
|
8536
|
+
risk: z13.enum(["critical", "high", "medium", "low"]).nullable().optional().describe(
|
|
8537
|
+
"Risk level \u2014 how much important surface the task touches (critical/high/medium/low). Pass null to clear."
|
|
8538
|
+
)
|
|
8333
8539
|
},
|
|
8334
|
-
async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch }) => {
|
|
8540
|
+
async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch, risk }) => {
|
|
8335
8541
|
try {
|
|
8336
|
-
const
|
|
8337
|
-
|
|
8542
|
+
const params = {
|
|
8543
|
+
title,
|
|
8544
|
+
storyPointValue,
|
|
8545
|
+
tagNames,
|
|
8546
|
+
githubPRUrl,
|
|
8547
|
+
githubBranch,
|
|
8548
|
+
risk
|
|
8549
|
+
};
|
|
8550
|
+
const updatedFields = describeUpdatedFields(params);
|
|
8551
|
+
if (updatedFields.length === 0) {
|
|
8338
8552
|
return textResult(
|
|
8339
8553
|
`No task properties were updated: none of the recognized keys were provided. Valid keys: ${VALID_PROPERTY_KEYS}. (Story points are set via 'storyPointValue', not 'storyPoints'.)`
|
|
8340
8554
|
);
|
|
8341
8555
|
}
|
|
8342
8556
|
await connection.call("updateTaskProperties", {
|
|
8343
8557
|
sessionId: connection.sessionId,
|
|
8344
|
-
|
|
8345
|
-
storyPointValue,
|
|
8346
|
-
tagNames,
|
|
8347
|
-
githubPRUrl,
|
|
8348
|
-
githubBranch
|
|
8558
|
+
...params
|
|
8349
8559
|
});
|
|
8350
|
-
const updatedFields = [];
|
|
8351
|
-
if (title !== void 0) updatedFields.push(`title to "${title}"`);
|
|
8352
|
-
if (storyPointValue !== void 0)
|
|
8353
|
-
updatedFields.push(`story points to ${storyPointValue}`);
|
|
8354
|
-
if (tagNames !== void 0) updatedFields.push(`tags (${tagNames.length} tag(s))`);
|
|
8355
|
-
if (githubPRUrl !== void 0) updatedFields.push(`PR link to "${githubPRUrl}"`);
|
|
8356
|
-
if (githubBranch !== void 0) updatedFields.push(`branch to "${githubBranch}"`);
|
|
8357
8560
|
return textResult(`Task properties updated: ${updatedFields.join(", ")}`);
|
|
8358
8561
|
} catch (error) {
|
|
8359
8562
|
return textResult(
|
|
@@ -8513,12 +8716,14 @@ function buildConveyorTools(connection, config, context, agentMode) {
|
|
|
8513
8716
|
const modeTools = getModeTools(effectiveMode, connection, config, context);
|
|
8514
8717
|
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" || effectiveMode === "chat" ? buildDiscoveryTools(connection) : [];
|
|
8515
8718
|
const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
|
|
8719
|
+
const handoffTools = config.mode === "pm" && (effectiveMode === "discovery" || effectiveMode === "auto") ? [buildHandoffTool(connection)] : [];
|
|
8516
8720
|
const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
|
|
8517
8721
|
const tools = withAlwaysLoad([
|
|
8518
8722
|
...commonTools,
|
|
8519
8723
|
...modeTools,
|
|
8520
8724
|
...discoveryTools,
|
|
8521
8725
|
...codeReviewTools,
|
|
8726
|
+
...handoffTools,
|
|
8522
8727
|
...emergencyTools
|
|
8523
8728
|
]);
|
|
8524
8729
|
if (effectiveMode === "chat") {
|
|
@@ -10504,7 +10709,7 @@ async function runUsageProbe(deps = {}) {
|
|
|
10504
10709
|
cols: 120,
|
|
10505
10710
|
rows: 45,
|
|
10506
10711
|
cwd,
|
|
10507
|
-
env: buildProbeEnv()
|
|
10712
|
+
env: buildProbeEnv(deps.env)
|
|
10508
10713
|
});
|
|
10509
10714
|
} catch {
|
|
10510
10715
|
resolve("");
|
|
@@ -12024,10 +12229,12 @@ export {
|
|
|
12024
12229
|
DEFAULT_LIFECYCLE_CONFIG,
|
|
12025
12230
|
Lifecycle,
|
|
12026
12231
|
defineTool,
|
|
12027
|
-
cleanTerminalOutput,
|
|
12028
12232
|
loadPtySpawn,
|
|
12029
12233
|
inheritedEnv,
|
|
12030
12234
|
buildPromptBytes,
|
|
12235
|
+
cleanTerminalOutput,
|
|
12236
|
+
buildSynthesizedCredentials,
|
|
12237
|
+
claudeJsonPath,
|
|
12031
12238
|
ClaudeTuiAdapter,
|
|
12032
12239
|
createServiceLogger,
|
|
12033
12240
|
PtyHarness,
|
|
@@ -12044,6 +12251,8 @@ export {
|
|
|
12044
12251
|
findOnPath,
|
|
12045
12252
|
resolvePlaywrightMcpServer,
|
|
12046
12253
|
resolveSessionStart,
|
|
12254
|
+
parseUsageGauges,
|
|
12255
|
+
runUsageProbe,
|
|
12047
12256
|
sampleKeyUsage,
|
|
12048
12257
|
awaitGitReady,
|
|
12049
12258
|
PortDiscovery,
|
|
@@ -12063,4 +12272,4 @@ export {
|
|
|
12063
12272
|
runStartCommand,
|
|
12064
12273
|
unshallowRepo
|
|
12065
12274
|
};
|
|
12066
|
-
//# sourceMappingURL=chunk-
|
|
12275
|
+
//# sourceMappingURL=chunk-PXQJ4NVO.js.map
|