@rallycry/conveyor-agent 10.13.72 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{boot-ZNL7X5LQ.js → boot-PKQ2I66D.js} +49 -2
- package/dist/{chunk-WMMBAKPE.js → chunk-GL2DIQEQ.js} +121 -8
- package/dist/{chunk-2L5THOWD.js → chunk-JQVAWRVL.js} +2 -2
- package/dist/{chunk-LSZ2KLJY.js → chunk-N4WSUTGV.js} +40 -3
- package/dist/{chunk-2SN32LM6.js → chunk-PEEGCZAR.js} +2055 -1035
- package/dist/cli.js +257 -100
- package/dist/index.d.ts +170 -3
- package/dist/index.js +3 -3
- package/dist/{serve-boot-4N3FRXVQ.js → serve-boot-YPAUKENG.js} +3 -3
- package/package.json +5 -4
- package/skills/conveyor-build/SKILL.md +262 -0
- package/skills/conveyor-build/references/pack-path.md +224 -0
- package/skills/conveyor-build/references/task-path.md +67 -0
- package/skills/conveyor-consensus/SKILL.md +99 -0
- package/skills/conveyor-consensus/references/doc-template.html +204 -0
- package/skills/conveyor-local-loop/SKILL.md +258 -0
- package/skills/conveyor-meeting-review/SKILL.md +72 -0
- package/skills/conveyor-plan/SKILL.md +177 -0
- package/skills/conveyor-plan/references/plan-format.md +134 -0
- package/skills/conveyor-review/SKILL.md +161 -0
- package/skills/conveyor-start/SKILL.md +106 -0
- package/skills/conveyor-triage/SKILL.md +174 -0
- package/skills/conveyor-workflows/SKILL.md +195 -0
- package/skills/conveyor-workflows/references/mcp-setup.md +43 -0
|
@@ -30,11 +30,11 @@ import {
|
|
|
30
30
|
statWorkspacePath,
|
|
31
31
|
updateRemoteToken,
|
|
32
32
|
verifyGitCredential
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-N4WSUTGV.js";
|
|
34
34
|
import {
|
|
35
35
|
registerBootMilestoneSocketFallback,
|
|
36
36
|
reportBootMilestone
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-GL2DIQEQ.js";
|
|
38
38
|
import {
|
|
39
39
|
LoopLagMonitor,
|
|
40
40
|
loopStatusForRunnerStatus
|
|
@@ -78,16 +78,140 @@ import {
|
|
|
78
78
|
sleep
|
|
79
79
|
} from "./chunk-W4LZ7R6Z.js";
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
function isPermissionDeniedError(err) {
|
|
83
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
-
return /insufficient permissions|authentication required/i.test(message);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ../shared/dist/chunk-6RHVH33O.js
|
|
81
|
+
// ../shared/dist/chunk-OKJPFFQI.js
|
|
88
82
|
var CARD_DESCRIPTION_MAX = 255;
|
|
89
83
|
var CARD_DESCRIPTION_LIMIT_MESSAGE = `Card descriptions are capped at ${CARD_DESCRIPTION_MAX} characters \u2014 write 1-2 plain sentences a non-engineer can read; put technical detail in the plan or card chat.`;
|
|
90
84
|
var CARD_DESCRIPTION_FIELD_HINT = `max ${CARD_DESCRIPTION_MAX} chars, 1-2 plain sentences a non-engineer can read \u2014 put technical detail in the plan`;
|
|
85
|
+
var SEVERITY_ENUM = [
|
|
86
|
+
"DEBUG",
|
|
87
|
+
"INFO",
|
|
88
|
+
"NOTICE",
|
|
89
|
+
"WARNING",
|
|
90
|
+
"ERROR",
|
|
91
|
+
"CRITICAL",
|
|
92
|
+
"ALERT",
|
|
93
|
+
"EMERGENCY"
|
|
94
|
+
];
|
|
95
|
+
var MAX_LINE_CHARS = 400;
|
|
96
|
+
var DEFAULT_SINCE_MINUTES = 60;
|
|
97
|
+
function truncateLine(text) {
|
|
98
|
+
const oneLine = text.replace(/\s*\n\s*/g, " \u23CE ");
|
|
99
|
+
if (oneLine.length <= MAX_LINE_CHARS) return oneLine;
|
|
100
|
+
const overflow = oneLine.length - MAX_LINE_CHARS;
|
|
101
|
+
return `${oneLine.slice(0, MAX_LINE_CHARS)}\u2026[+${overflow}c]`;
|
|
102
|
+
}
|
|
103
|
+
function entrySource(entry) {
|
|
104
|
+
return entry.resource.service_name ?? entry.resource.pod_name ?? entry.resource.database_id ?? entry.resourceType ?? "-";
|
|
105
|
+
}
|
|
106
|
+
var PAYLOAD_SKIP_KEYS = /* @__PURE__ */ new Set(["message", "severity", "timestamp", "level", "stack"]);
|
|
107
|
+
var PAYLOAD_PRIORITY = [
|
|
108
|
+
"error",
|
|
109
|
+
"outcome",
|
|
110
|
+
"serviceName",
|
|
111
|
+
"methodName",
|
|
112
|
+
"userId",
|
|
113
|
+
"taskId",
|
|
114
|
+
"sessionId",
|
|
115
|
+
"workspaceId",
|
|
116
|
+
"projectId",
|
|
117
|
+
"durationMs"
|
|
118
|
+
];
|
|
119
|
+
var PAYLOAD_VALUE_MAX_CHARS = 160;
|
|
120
|
+
function compactPayloadValue(value) {
|
|
121
|
+
const raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
122
|
+
const flat = (raw ?? "undefined").replace(/\s+/g, " ");
|
|
123
|
+
const quoted = typeof value === "string" && /[\s"]/.test(flat) ? JSON.stringify(flat) : flat;
|
|
124
|
+
return quoted.length > PAYLOAD_VALUE_MAX_CHARS ? `${quoted.slice(0, PAYLOAD_VALUE_MAX_CHARS)}\u2026` : quoted;
|
|
125
|
+
}
|
|
126
|
+
function formatPayloadSuffix(payloadJson) {
|
|
127
|
+
if (!payloadJson) return "";
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(payloadJson);
|
|
131
|
+
} catch {
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "";
|
|
135
|
+
const obj = parsed;
|
|
136
|
+
const rank = (key) => {
|
|
137
|
+
const i = PAYLOAD_PRIORITY.indexOf(key);
|
|
138
|
+
return i === -1 ? PAYLOAD_PRIORITY.length : i;
|
|
139
|
+
};
|
|
140
|
+
const parts = Object.keys(obj).filter((k) => !PAYLOAD_SKIP_KEYS.has(k) && obj[k] !== void 0 && obj[k] !== null).sort((a, b) => rank(a) - rank(b) || a.localeCompare(b)).map((k) => `${k}=${compactPayloadValue(obj[k])}`);
|
|
141
|
+
return parts.length > 0 ? ` | ${parts.join(" ")}` : "";
|
|
142
|
+
}
|
|
143
|
+
function formatLogEntryLine(entry) {
|
|
144
|
+
const httpPrefix = entry.httpRequest?.status ? `http ${entry.httpRequest.status} ${entry.httpRequest.method ?? ""} ${entry.httpRequest.url ?? ""}`.trim() + " \u2014 " : "";
|
|
145
|
+
return `${entry.timestamp} ${entry.severity.padEnd(7)} [${entrySource(entry)}] ${truncateLine(
|
|
146
|
+
`${httpPrefix}${entry.message}${formatPayloadSuffix(entry.payload)}`
|
|
147
|
+
)}`;
|
|
148
|
+
}
|
|
149
|
+
async function runQueryGcpLogs(port, params, now = Date.now) {
|
|
150
|
+
const startTime = params.startTime ?? new Date(now() - (params.sinceMinutes ?? DEFAULT_SINCE_MINUTES) * 6e4).toISOString();
|
|
151
|
+
const result = await port.queryGcpLogs({
|
|
152
|
+
projectId: params.projectId,
|
|
153
|
+
env: params.env,
|
|
154
|
+
severity: params.severity,
|
|
155
|
+
services: params.services,
|
|
156
|
+
sqlInstances: params.sqlInstances,
|
|
157
|
+
allServices: params.allServices,
|
|
158
|
+
search: params.search,
|
|
159
|
+
filter: params.filter,
|
|
160
|
+
startTime,
|
|
161
|
+
endTime: params.endTime,
|
|
162
|
+
limit: params.limit,
|
|
163
|
+
pageToken: params.pageToken
|
|
164
|
+
});
|
|
165
|
+
if (result.error) return result.error;
|
|
166
|
+
const header = [
|
|
167
|
+
`env=${params.env ?? "prod"}`,
|
|
168
|
+
`window=${startTime}\u2192${params.endTime ?? "now"}`,
|
|
169
|
+
...params.severity ? [`minSeverity=${params.severity}`] : [],
|
|
170
|
+
`scope=${result.scopedServices ? `[${result.scopedServices.join(", ")}]` : "all"}`,
|
|
171
|
+
`entries=${result.entries.length}`
|
|
172
|
+
].join(" ");
|
|
173
|
+
const lines = result.entries.map(formatLogEntryLine);
|
|
174
|
+
const footer = result.nextPageToken ? [`-- more available: pass pageToken="${result.nextPageToken}" to continue`] : [];
|
|
175
|
+
if (lines.length === 0) {
|
|
176
|
+
return [
|
|
177
|
+
header,
|
|
178
|
+
"(no matching log entries \u2014 widen the window, lower minSeverity, or drop filters)"
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
return [header, ...lines, ...footer].join("\n");
|
|
182
|
+
}
|
|
183
|
+
async function runQueryGrafanaLogs(port, params, now = Date.now) {
|
|
184
|
+
const startTime = params.startTime ?? new Date(now() - (params.sinceMinutes ?? DEFAULT_SINCE_MINUTES) * 6e4).toISOString();
|
|
185
|
+
const result = await port.queryGrafanaLogs({
|
|
186
|
+
projectId: params.projectId,
|
|
187
|
+
env: params.env,
|
|
188
|
+
level: params.level,
|
|
189
|
+
services: params.services,
|
|
190
|
+
search: params.search,
|
|
191
|
+
logql: params.logql,
|
|
192
|
+
startTime,
|
|
193
|
+
endTime: params.endTime,
|
|
194
|
+
limit: params.limit
|
|
195
|
+
});
|
|
196
|
+
if (result.error) return result.error;
|
|
197
|
+
const header = [
|
|
198
|
+
`env=${params.env ?? "prod"}`,
|
|
199
|
+
`window=${startTime}\u2192${params.endTime ?? "now"}`,
|
|
200
|
+
...params.level ? [`minLevel=${params.level}`] : [],
|
|
201
|
+
...result.logql ? [`logql=${truncateLine(result.logql)}`] : [],
|
|
202
|
+
`entries=${result.entries.length}`
|
|
203
|
+
].join(" ");
|
|
204
|
+
const lines = result.entries.map(formatLogEntryLine);
|
|
205
|
+
const oldest = result.entries.map((e) => e.timestamp).sort()[0];
|
|
206
|
+
const footer = result.hasMore ? [`-- hit the limit: older lines exist \u2014 pass endTime="${oldest}" to page further back`] : [];
|
|
207
|
+
if (lines.length === 0) {
|
|
208
|
+
return [
|
|
209
|
+
header,
|
|
210
|
+
"(no matching log entries \u2014 widen the window, lower minLevel, or drop filters)"
|
|
211
|
+
].join("\n");
|
|
212
|
+
}
|
|
213
|
+
return [header, ...lines, ...footer].join("\n");
|
|
214
|
+
}
|
|
91
215
|
|
|
92
216
|
// ../shared/dist/index.js
|
|
93
217
|
import { z } from "zod";
|
|
@@ -97,6 +221,8 @@ import { z as z4 } from "zod";
|
|
|
97
221
|
import { z as z5 } from "zod";
|
|
98
222
|
import { z as z6 } from "zod";
|
|
99
223
|
import { z as z7 } from "zod";
|
|
224
|
+
import { z as z8 } from "zod";
|
|
225
|
+
import { z as z9 } from "zod";
|
|
100
226
|
var EXTERNAL_AGENT_MESSAGE_SOURCE = "external_agent";
|
|
101
227
|
var DEFAULT_SONNET_MODEL = "claude-sonnet-5";
|
|
102
228
|
var DEFAULT_OPUS_MODEL = "claude-opus-5";
|
|
@@ -282,12 +408,24 @@ var ACTIVE_WORK_STATUSES = [
|
|
|
282
408
|
"Complete"
|
|
283
409
|
];
|
|
284
410
|
var IDENTIFIED_WORK_STATUSES = ["Open", ...ACTIVE_WORK_STATUSES];
|
|
411
|
+
var DEFAULT_TASK_STATUS_COLOR = "#a8a29e";
|
|
412
|
+
var DEFAULT_TASK_STATUS_COLOR_INT = Number.parseInt(DEFAULT_TASK_STATUS_COLOR.slice(1), 16);
|
|
285
413
|
var MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
|
|
286
414
|
var MAX_FILE_TAGS = 5;
|
|
287
415
|
var MAX_FILE_TAG_LENGTH = 100;
|
|
288
416
|
var EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
|
|
289
417
|
var EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
|
|
290
418
|
var IDLE_HEARTBEAT_MS = 90 * 1e3;
|
|
419
|
+
var RUNNER_MODES = [
|
|
420
|
+
"task",
|
|
421
|
+
"plan",
|
|
422
|
+
"pm",
|
|
423
|
+
"code-review",
|
|
424
|
+
"adhoc",
|
|
425
|
+
"pack",
|
|
426
|
+
"shell",
|
|
427
|
+
"serving"
|
|
428
|
+
];
|
|
291
429
|
var TurnEndToolCallSchema = z3.object({
|
|
292
430
|
tool: z3.string(),
|
|
293
431
|
input: z3.string().optional(),
|
|
@@ -845,6 +983,9 @@ var ReportReviewSpawnFailureRequestSchema = z4.object({
|
|
|
845
983
|
reviewSessionId: z4.string(),
|
|
846
984
|
error: z4.string().max(2e3).optional()
|
|
847
985
|
});
|
|
986
|
+
var ReportBuilderSpawnFailureRequestSchema = ReportReviewSpawnFailureRequestSchema.omit({
|
|
987
|
+
reviewSessionId: true
|
|
988
|
+
}).extend({ buildSessionId: z4.string() });
|
|
848
989
|
var RequestWorkspaceRecycleRequestSchema = z4.object({
|
|
849
990
|
sessionId: z4.string(),
|
|
850
991
|
reason: z4.string().max(2e3)
|
|
@@ -1497,12 +1638,46 @@ var CreateProjectSuggestionRequestSchema = z5.object({
|
|
|
1497
1638
|
tagNames: z5.array(z5.string()).optional(),
|
|
1498
1639
|
requestingUserId: z5.string().optional()
|
|
1499
1640
|
});
|
|
1641
|
+
var ListProjectChannelsRequestSchema = z6.object({
|
|
1642
|
+
projectId: z6.string()
|
|
1643
|
+
});
|
|
1644
|
+
var READ_CHANNEL_MESSAGES_MAX_LIMIT = 50;
|
|
1645
|
+
var ReadChannelMessagesRequestSchema = z6.object({
|
|
1646
|
+
projectId: z6.string(),
|
|
1647
|
+
channelId: z6.string().min(1).max(200),
|
|
1648
|
+
limit: z6.number().int().min(1).max(READ_CHANNEL_MESSAGES_MAX_LIMIT).optional(),
|
|
1649
|
+
/** Provider-native cursor: return messages OLDER than this one. */
|
|
1650
|
+
before: z6.string().max(100).optional(),
|
|
1651
|
+
/** Provider-native cursor: return messages NEWER than this one. */
|
|
1652
|
+
after: z6.string().max(100).optional(),
|
|
1653
|
+
/**
|
|
1654
|
+
* Read one thread instead of the channel surface. Slack calls this a
|
|
1655
|
+
* `thread_ts`; on Discord it is the thread channel's id. One field for both,
|
|
1656
|
+
* because a caller holding a `threadTs` from a previous read should not have
|
|
1657
|
+
* to know which provider produced it.
|
|
1658
|
+
*/
|
|
1659
|
+
threadTs: z6.string().max(100).optional()
|
|
1660
|
+
});
|
|
1661
|
+
var POST_CHANNEL_MESSAGE_MAX_CHARS = 1800;
|
|
1662
|
+
var PostChannelMessageRequestSchema = z6.object({
|
|
1663
|
+
projectId: z6.string(),
|
|
1664
|
+
channelId: z6.string().min(1).max(200),
|
|
1665
|
+
text: z6.string().min(1).max(POST_CHANNEL_MESSAGE_MAX_CHARS),
|
|
1666
|
+
/** Reply inside a thread rather than to the channel. */
|
|
1667
|
+
threadTs: z6.string().max(100).optional()
|
|
1668
|
+
});
|
|
1669
|
+
var GET_ANALYTICS_SUMMARY_MAX_RANGE_DAYS = 90;
|
|
1670
|
+
var GetProjectAnalyticsSummaryRequestSchema = z6.object({
|
|
1671
|
+
projectId: z6.string(),
|
|
1672
|
+
rangeDays: z6.number().int().min(1).max(GET_ANALYTICS_SUMMARY_MAX_RANGE_DAYS).optional(),
|
|
1673
|
+
campaign: z6.string().max(200).optional()
|
|
1674
|
+
});
|
|
1500
1675
|
var SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
1501
|
-
var ReviewGuideFileReferenceSchema =
|
|
1502
|
-
path:
|
|
1503
|
-
startLine:
|
|
1504
|
-
endLine:
|
|
1505
|
-
hunkHeader:
|
|
1676
|
+
var ReviewGuideFileReferenceSchema = z7.object({
|
|
1677
|
+
path: z7.string().min(1).max(500),
|
|
1678
|
+
startLine: z7.number().int().positive().max(1e6).optional(),
|
|
1679
|
+
endLine: z7.number().int().positive().max(1e6).optional(),
|
|
1680
|
+
hunkHeader: z7.string().min(1).max(300).optional()
|
|
1506
1681
|
}).strict().superRefine((value, ctx) => {
|
|
1507
1682
|
if (value.endLine !== void 0 && value.startLine === void 0) {
|
|
1508
1683
|
ctx.addIssue({
|
|
@@ -1519,19 +1694,19 @@ var ReviewGuideFileReferenceSchema = z6.object({
|
|
|
1519
1694
|
});
|
|
1520
1695
|
}
|
|
1521
1696
|
});
|
|
1522
|
-
var ReviewGuideSectionSchema =
|
|
1523
|
-
title:
|
|
1524
|
-
explanation:
|
|
1525
|
-
classification:
|
|
1526
|
-
files:
|
|
1697
|
+
var ReviewGuideSectionSchema = z7.object({
|
|
1698
|
+
title: z7.string().min(1).max(160),
|
|
1699
|
+
explanation: z7.string().min(1).max(2e3),
|
|
1700
|
+
classification: z7.enum(["core", "supporting"]).optional(),
|
|
1701
|
+
files: z7.array(ReviewGuideFileReferenceSchema).min(1).max(20)
|
|
1527
1702
|
}).strict();
|
|
1528
|
-
var ReviewGuideContentSchema =
|
|
1529
|
-
overview:
|
|
1530
|
-
sections:
|
|
1703
|
+
var ReviewGuideContentSchema = z7.object({
|
|
1704
|
+
overview: z7.string().min(1).max(3e3),
|
|
1705
|
+
sections: z7.array(ReviewGuideSectionSchema).min(1).max(12)
|
|
1531
1706
|
}).strict();
|
|
1532
1707
|
var PublishReviewGuideRequestSchema = ReviewGuideContentSchema.extend({
|
|
1533
|
-
sessionId:
|
|
1534
|
-
reviewedSha:
|
|
1708
|
+
sessionId: z7.string().min(1),
|
|
1709
|
+
reviewedSha: z7.string().regex(SHA_PATTERN, "reviewedSha must be a 40-character commit SHA")
|
|
1535
1710
|
}).strict();
|
|
1536
1711
|
var CONTEXT_LINK_LOCATOR_MAX = 300;
|
|
1537
1712
|
var TEST_TITLE = /\b(?:it|test|describe)(?:\.\w+)?\s*\(\s*(['"`])((?:(?!\1)[\s\S])*)\1/g;
|
|
@@ -1548,130 +1723,130 @@ function locatorMatchesContent(content, locatorType, locator) {
|
|
|
1548
1723
|
var TAG_DESCRIPTION_MAX = CARD_DESCRIPTION_MAX;
|
|
1549
1724
|
var TAG_OVERVIEW_MAX = 32e3;
|
|
1550
1725
|
var TAG_REASON_MAX = 500;
|
|
1551
|
-
var ProjectTagContextPathSchema =
|
|
1552
|
-
type:
|
|
1553
|
-
path:
|
|
1554
|
-
label:
|
|
1726
|
+
var ProjectTagContextPathSchema = z8.object({
|
|
1727
|
+
type: z8.enum(["rule", "doc", "file", "folder"]),
|
|
1728
|
+
path: z8.string().min(1).max(500),
|
|
1729
|
+
label: z8.string().max(100).optional(),
|
|
1555
1730
|
/** Verified-link tether — text that must keep existing in the file. */
|
|
1556
|
-
locator:
|
|
1731
|
+
locator: z8.string().min(1).max(CONTEXT_LINK_LOCATOR_MAX).regex(/^[^\r\n]*$/, "Locator cannot contain line breaks").optional(),
|
|
1557
1732
|
/** test = must appear in a real test/describe title; code = any substring. */
|
|
1558
|
-
locatorType:
|
|
1733
|
+
locatorType: z8.enum(["test", "code"]).optional()
|
|
1559
1734
|
}).refine((link) => link.locator === void 0 === (link.locatorType === void 0), {
|
|
1560
1735
|
message: "locator and locatorType must be provided together"
|
|
1561
1736
|
}).refine((link) => link.locator === void 0 || link.type !== "folder", {
|
|
1562
1737
|
message: "folder links cannot carry a locator"
|
|
1563
1738
|
});
|
|
1564
|
-
var hexColor =
|
|
1565
|
-
var overviewPathSchema =
|
|
1566
|
-
var CreateProjectTagRequestSchema =
|
|
1567
|
-
projectId:
|
|
1568
|
-
name:
|
|
1739
|
+
var hexColor = z8.string().regex(/^#[0-9a-fA-F]{6}$/, "Expected #RRGGBB hex color");
|
|
1740
|
+
var overviewPathSchema = z8.string().min(1).max(500).regex(/^[^\r\n]*$/, "Overview path cannot contain line breaks");
|
|
1741
|
+
var CreateProjectTagRequestSchema = z8.object({
|
|
1742
|
+
projectId: z8.string(),
|
|
1743
|
+
name: z8.string().min(1).max(50),
|
|
1569
1744
|
color: hexColor.optional(),
|
|
1570
|
-
description:
|
|
1571
|
-
overview:
|
|
1745
|
+
description: z8.string().max(TAG_DESCRIPTION_MAX).optional(),
|
|
1746
|
+
overview: z8.string().max(TAG_OVERVIEW_MAX).optional(),
|
|
1572
1747
|
/** Source the overview from this repo file (stored overview stays as the pending fallback). */
|
|
1573
1748
|
overviewPath: overviewPathSchema.optional(),
|
|
1574
|
-
contextPaths:
|
|
1749
|
+
contextPaths: z8.array(ProjectTagContextPathSchema).max(20).optional(),
|
|
1575
1750
|
/** Parents to link at create time (multi-parent DAG). */
|
|
1576
|
-
parentTagIds:
|
|
1577
|
-
requestingUserId:
|
|
1751
|
+
parentTagIds: z8.array(z8.string()).max(25).optional(),
|
|
1752
|
+
requestingUserId: z8.string().optional()
|
|
1578
1753
|
});
|
|
1579
|
-
var UpdateProjectTagRequestSchema =
|
|
1580
|
-
projectId:
|
|
1581
|
-
tagId:
|
|
1582
|
-
name:
|
|
1754
|
+
var UpdateProjectTagRequestSchema = z8.object({
|
|
1755
|
+
projectId: z8.string(),
|
|
1756
|
+
tagId: z8.string(),
|
|
1757
|
+
name: z8.string().min(1).max(50).optional(),
|
|
1583
1758
|
color: hexColor.optional(),
|
|
1584
|
-
description:
|
|
1759
|
+
description: z8.string().max(TAG_DESCRIPTION_MAX).optional(),
|
|
1585
1760
|
/** Full markdown glossary body; null clears it. Rejected while overviewPath is set. */
|
|
1586
|
-
overview:
|
|
1761
|
+
overview: z8.string().max(TAG_OVERVIEW_MAX).nullable().optional(),
|
|
1587
1762
|
/** Repo file to source the overview from; null clears back to the stored overview. */
|
|
1588
1763
|
overviewPath: overviewPathSchema.nullable().optional(),
|
|
1589
1764
|
/** Full replacement of the tag's context links when provided. */
|
|
1590
|
-
contextPaths:
|
|
1765
|
+
contextPaths: z8.array(ProjectTagContextPathSchema).max(20).optional(),
|
|
1591
1766
|
/** Full-set replacement of the tag's parent tags (multi-parent DAG). */
|
|
1592
|
-
parentTagIds:
|
|
1767
|
+
parentTagIds: z8.array(z8.string()).max(25).optional(),
|
|
1593
1768
|
/** One-line revision provenance, recorded in the tag's history. */
|
|
1594
|
-
reason:
|
|
1769
|
+
reason: z8.string().max(TAG_REASON_MAX).optional(),
|
|
1595
1770
|
/** Card the caller was working in — stamped into the revision history. */
|
|
1596
|
-
taskId:
|
|
1597
|
-
requestingUserId:
|
|
1771
|
+
taskId: z8.string().optional(),
|
|
1772
|
+
requestingUserId: z8.string().optional()
|
|
1598
1773
|
});
|
|
1599
|
-
var PostToProjectChatRequestSchema =
|
|
1600
|
-
projectId:
|
|
1601
|
-
content:
|
|
1602
|
-
requestingUserId:
|
|
1774
|
+
var PostToProjectChatRequestSchema = z8.object({
|
|
1775
|
+
projectId: z8.string(),
|
|
1776
|
+
content: z8.string().min(1).max(2e4),
|
|
1777
|
+
requestingUserId: z8.string().optional(),
|
|
1603
1778
|
/** Marks the post so the server can persist it beyond chat (tag-audit summaries land in tag history). */
|
|
1604
|
-
kind:
|
|
1605
|
-
});
|
|
1606
|
-
var StartTagAuditRequestSchema =
|
|
1607
|
-
projectId:
|
|
1608
|
-
requestingUserId:
|
|
1609
|
-
});
|
|
1610
|
-
var StartTaskAuditRequestSchema =
|
|
1611
|
-
projectId:
|
|
1612
|
-
taskIds:
|
|
1613
|
-
requestingUserId:
|
|
1614
|
-
});
|
|
1615
|
-
var GetActiveAuditSessionsRequestSchema =
|
|
1616
|
-
projectId:
|
|
1617
|
-
});
|
|
1618
|
-
var ReportTaskAuditResultRequestSchema =
|
|
1619
|
-
projectId:
|
|
1620
|
-
taskId:
|
|
1621
|
-
summary:
|
|
1622
|
-
turnGrades:
|
|
1623
|
-
|
|
1624
|
-
turnIndex:
|
|
1625
|
-
phase:
|
|
1626
|
-
grade:
|
|
1627
|
-
reasoning:
|
|
1628
|
-
eventType:
|
|
1629
|
-
eventSummary:
|
|
1779
|
+
kind: z8.enum(["tag_audit_summary"]).optional()
|
|
1780
|
+
});
|
|
1781
|
+
var StartTagAuditRequestSchema = z8.object({
|
|
1782
|
+
projectId: z8.string(),
|
|
1783
|
+
requestingUserId: z8.string().optional()
|
|
1784
|
+
});
|
|
1785
|
+
var StartTaskAuditRequestSchema = z8.object({
|
|
1786
|
+
projectId: z8.string(),
|
|
1787
|
+
taskIds: z8.array(z8.string()).min(1).max(20),
|
|
1788
|
+
requestingUserId: z8.string().optional()
|
|
1789
|
+
});
|
|
1790
|
+
var GetActiveAuditSessionsRequestSchema = z8.object({
|
|
1791
|
+
projectId: z8.string()
|
|
1792
|
+
});
|
|
1793
|
+
var ReportTaskAuditResultRequestSchema = z8.object({
|
|
1794
|
+
projectId: z8.string(),
|
|
1795
|
+
taskId: z8.string(),
|
|
1796
|
+
summary: z8.string(),
|
|
1797
|
+
turnGrades: z8.array(
|
|
1798
|
+
z8.object({
|
|
1799
|
+
turnIndex: z8.number(),
|
|
1800
|
+
phase: z8.enum(["planning", "building", "human"]),
|
|
1801
|
+
grade: z8.enum(["correct", "neutral", "blunder"]),
|
|
1802
|
+
reasoning: z8.string(),
|
|
1803
|
+
eventType: z8.string(),
|
|
1804
|
+
eventSummary: z8.string()
|
|
1630
1805
|
})
|
|
1631
1806
|
),
|
|
1632
|
-
planningAccuracy:
|
|
1633
|
-
buildingAccuracy:
|
|
1634
|
-
humanAccuracy:
|
|
1635
|
-
planningCorrect:
|
|
1636
|
-
planningNeutral:
|
|
1637
|
-
planningBlunder:
|
|
1638
|
-
buildingCorrect:
|
|
1639
|
-
buildingNeutral:
|
|
1640
|
-
buildingBlunder:
|
|
1641
|
-
humanCorrect:
|
|
1642
|
-
humanNeutral:
|
|
1643
|
-
humanBlunder:
|
|
1644
|
-
humanEvaluations:
|
|
1645
|
-
|
|
1646
|
-
messageIndex:
|
|
1647
|
-
rating:
|
|
1648
|
-
reasoning:
|
|
1807
|
+
planningAccuracy: z8.number().nullable(),
|
|
1808
|
+
buildingAccuracy: z8.number().nullable(),
|
|
1809
|
+
humanAccuracy: z8.number().nullable(),
|
|
1810
|
+
planningCorrect: z8.number(),
|
|
1811
|
+
planningNeutral: z8.number(),
|
|
1812
|
+
planningBlunder: z8.number(),
|
|
1813
|
+
buildingCorrect: z8.number(),
|
|
1814
|
+
buildingNeutral: z8.number(),
|
|
1815
|
+
buildingBlunder: z8.number(),
|
|
1816
|
+
humanCorrect: z8.number(),
|
|
1817
|
+
humanNeutral: z8.number(),
|
|
1818
|
+
humanBlunder: z8.number(),
|
|
1819
|
+
humanEvaluations: z8.array(
|
|
1820
|
+
z8.object({
|
|
1821
|
+
messageIndex: z8.number(),
|
|
1822
|
+
rating: z8.union([z8.literal(-1), z8.literal(0), z8.literal(1)]),
|
|
1823
|
+
reasoning: z8.string()
|
|
1649
1824
|
})
|
|
1650
1825
|
).optional(),
|
|
1651
|
-
suggestionIds:
|
|
1652
|
-
auditCostUsd:
|
|
1653
|
-
model:
|
|
1826
|
+
suggestionIds: z8.array(z8.string()),
|
|
1827
|
+
auditCostUsd: z8.number().nullable(),
|
|
1828
|
+
model: z8.string().nullable(),
|
|
1654
1829
|
/** When set, the audit is marked failed with this message instead. */
|
|
1655
|
-
error:
|
|
1830
|
+
error: z8.string().optional()
|
|
1656
1831
|
});
|
|
1657
|
-
var GetTaskAuditsRequestSchema =
|
|
1658
|
-
projectId:
|
|
1659
|
-
limit:
|
|
1832
|
+
var GetTaskAuditsRequestSchema = z8.object({
|
|
1833
|
+
projectId: z8.string(),
|
|
1834
|
+
limit: z8.number().int().positive().max(200).optional().default(50)
|
|
1660
1835
|
});
|
|
1661
|
-
var GetTaskAuditRequestSchema =
|
|
1662
|
-
projectId:
|
|
1663
|
-
auditId:
|
|
1836
|
+
var GetTaskAuditRequestSchema = z8.object({
|
|
1837
|
+
projectId: z8.string(),
|
|
1838
|
+
auditId: z8.string()
|
|
1664
1839
|
});
|
|
1665
|
-
var GetTaskAuditAggregatesRequestSchema =
|
|
1666
|
-
projectId:
|
|
1840
|
+
var GetTaskAuditAggregatesRequestSchema = z8.object({
|
|
1841
|
+
projectId: z8.string()
|
|
1667
1842
|
});
|
|
1668
|
-
var DeleteTaskAuditRequestSchema =
|
|
1669
|
-
projectId:
|
|
1670
|
-
auditId:
|
|
1671
|
-
requestingUserId:
|
|
1843
|
+
var DeleteTaskAuditRequestSchema = z8.object({
|
|
1844
|
+
projectId: z8.string(),
|
|
1845
|
+
auditId: z8.string(),
|
|
1846
|
+
requestingUserId: z8.string().optional()
|
|
1672
1847
|
});
|
|
1673
|
-
var MarkInitialPromptSubmittedRequestSchema =
|
|
1674
|
-
sessionId:
|
|
1848
|
+
var MarkInitialPromptSubmittedRequestSchema = z8.object({
|
|
1849
|
+
sessionId: z8.string()
|
|
1675
1850
|
});
|
|
1676
1851
|
var CRITICAL_AUTOMATED_SOURCES = /* @__PURE__ */ new Set([
|
|
1677
1852
|
"ci_failure",
|
|
@@ -1684,6 +1859,52 @@ var CRITICAL_AUTOMATED_SOURCES = /* @__PURE__ */ new Set([
|
|
|
1684
1859
|
// reported completed for a prior turn. Only ever sent to parent tasks.
|
|
1685
1860
|
"parent"
|
|
1686
1861
|
]);
|
|
1862
|
+
var PACK_EXECUTIONS = ["single-pod", "fan-out"];
|
|
1863
|
+
var DEFAULT_PACK_EXECUTION = "single-pod";
|
|
1864
|
+
function parsePackExecution(value) {
|
|
1865
|
+
return PACK_EXECUTIONS.includes(value ?? "") ? value : DEFAULT_PACK_EXECUTION;
|
|
1866
|
+
}
|
|
1867
|
+
var MEETING_TRANSCRIPT_MAX_CHARS = 2e6;
|
|
1868
|
+
var MEETING_TITLE_MAX = 200;
|
|
1869
|
+
var CreateMeetingFromTranscriptRequestSchema = z9.object({
|
|
1870
|
+
projectId: z9.string().cuid(),
|
|
1871
|
+
rawText: z9.string().min(1).max(MEETING_TRANSCRIPT_MAX_CHARS),
|
|
1872
|
+
title: z9.string().min(1).max(MEETING_TITLE_MAX).optional(),
|
|
1873
|
+
/** ISO 8601. Defaults to now when the source carries no date. */
|
|
1874
|
+
occurredAt: z9.string().datetime().optional(),
|
|
1875
|
+
/** Override auto-detection. Rarely needed; detection handles the three formats. */
|
|
1876
|
+
format: z9.enum(["text", "vtt", "srt"]).optional(),
|
|
1877
|
+
source: z9.enum(["manual", "slack"]).optional()
|
|
1878
|
+
});
|
|
1879
|
+
var GetMeetingRequestSchema = z9.object({
|
|
1880
|
+
projectId: z9.string().cuid(),
|
|
1881
|
+
meetingId: z9.string().cuid()
|
|
1882
|
+
});
|
|
1883
|
+
var UpdateMeetingRequestSchema = z9.object({
|
|
1884
|
+
projectId: z9.string().cuid(),
|
|
1885
|
+
meetingId: z9.string().cuid(),
|
|
1886
|
+
title: z9.string().min(1).max(MEETING_TITLE_MAX).optional(),
|
|
1887
|
+
occurredAt: z9.string().datetime().optional()
|
|
1888
|
+
});
|
|
1889
|
+
var RegenerateMeetingSummaryRequestSchema = z9.object({
|
|
1890
|
+
projectId: z9.string().cuid(),
|
|
1891
|
+
meetingId: z9.string().cuid()
|
|
1892
|
+
});
|
|
1893
|
+
var DeleteMeetingRequestSchema = z9.object({
|
|
1894
|
+
projectId: z9.string().cuid(),
|
|
1895
|
+
meetingId: z9.string().cuid()
|
|
1896
|
+
});
|
|
1897
|
+
var ListMeetingsRequestSchema = z9.object({
|
|
1898
|
+
projectId: z9.string().cuid(),
|
|
1899
|
+
limit: z9.number().int().min(1).max(50).optional(),
|
|
1900
|
+
search: z9.string().max(200).optional()
|
|
1901
|
+
});
|
|
1902
|
+
var ReadMeetingTranscriptRequestSchema = z9.object({
|
|
1903
|
+
projectId: z9.string().cuid(),
|
|
1904
|
+
meetingId: z9.string().cuid(),
|
|
1905
|
+
offset: z9.number().int().min(0).optional(),
|
|
1906
|
+
limit: z9.number().int().min(1).max(500).optional()
|
|
1907
|
+
});
|
|
1687
1908
|
var AGENT_STATUS_REASON_USER_QUESTION = "user_question";
|
|
1688
1909
|
var TASK_CHAT_HISTORY_LIMIT = 20;
|
|
1689
1910
|
var PM_CHAT_HISTORY_LIMIT = 40;
|
|
@@ -2034,6 +2255,12 @@ var surfaceTypes = (surface) => Object.keys(CARD_TYPE_SURFACE).filter(
|
|
|
2034
2255
|
var BOARD_CARD_TYPES = surfaceTypes("board");
|
|
2035
2256
|
var REPORT_CARD_TYPES = surfaceTypes("report");
|
|
2036
2257
|
|
|
2258
|
+
// src/connection/auth-errors.ts
|
|
2259
|
+
function isPermissionDeniedError(err) {
|
|
2260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2261
|
+
return /insufficient permissions|authentication required/i.test(message);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2037
2264
|
// src/runner/mode-controller.ts
|
|
2038
2265
|
var ModeController = class {
|
|
2039
2266
|
_mode;
|
|
@@ -2041,10 +2268,46 @@ var ModeController = class {
|
|
|
2041
2268
|
_pendingModeRestart = false;
|
|
2042
2269
|
_runnerMode;
|
|
2043
2270
|
_isAuto;
|
|
2271
|
+
/**
|
|
2272
|
+
* Which planner this is. Only read when `_runnerMode` is `plan`; see
|
|
2273
|
+
* `effectiveMode` for the full reasoning, and `latchPlanFlavor` for why this
|
|
2274
|
+
* latches toward `chat` and never away from it.
|
|
2275
|
+
*/
|
|
2276
|
+
_planFlavor = "discovery";
|
|
2044
2277
|
constructor(initialMode, runnerMode = "task", isAuto = false) {
|
|
2045
2278
|
this._mode = initialMode;
|
|
2046
2279
|
this._runnerMode = runnerMode;
|
|
2047
2280
|
this._isAuto = isAuto;
|
|
2281
|
+
this.latchPlanFlavor(initialMode);
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Promote this planner to the chat flavor, once, and never demote it.
|
|
2285
|
+
*
|
|
2286
|
+
* It cannot be a constructor-only assignment, because the constructor usually
|
|
2287
|
+
* has nothing to go on. A GKE pod's bootstrap bundle carries no agentMode, so
|
|
2288
|
+
* `CONVEYOR_AGENT_MODE` is unset and `initialMode` falls back to `"building"`
|
|
2289
|
+
* for EVERY pod — which is the whole reason `applyServerMode` exists. Pinning
|
|
2290
|
+
* the flavor at construction alone would therefore make every chat card in the
|
|
2291
|
+
* cloud a discovery planner and silently re-open the defect this replaced.
|
|
2292
|
+
*
|
|
2293
|
+
* One-way, and both directions matter:
|
|
2294
|
+
*
|
|
2295
|
+
* - Only the literal card mode `chat` promotes. The server reports `chat` only
|
|
2296
|
+
* for a card whose agentMode IS `chat`, so a discovery card can never be
|
|
2297
|
+
* talked into the build-capable flavor. The original hazard — `auto` stamped
|
|
2298
|
+
* onto a plan-less auto card's planner — is untouched, because `auto` is not
|
|
2299
|
+
* `chat`.
|
|
2300
|
+
* - Nothing demotes. The Build press overwrites `Task.agentMode` to
|
|
2301
|
+
* `auto`/`building` before the server sees it, so a later stamp would
|
|
2302
|
+
* otherwise strip a live conversation's prompt and file deliverables
|
|
2303
|
+
* mid-turn.
|
|
2304
|
+
*
|
|
2305
|
+
* The asymmetry with `auto` is principled rather than convenient: an auto
|
|
2306
|
+
* card's mode and its planner phase legitimately differ, while a chat card's
|
|
2307
|
+
* mode IS what its session is, for the session's whole life.
|
|
2308
|
+
*/
|
|
2309
|
+
latchPlanFlavor(agentMode) {
|
|
2310
|
+
if (agentMode === "chat") this._planFlavor = "chat";
|
|
2048
2311
|
}
|
|
2049
2312
|
// ── Getters ────────────────────────────────────────────────────────
|
|
2050
2313
|
get mode() {
|
|
@@ -2065,8 +2328,28 @@ var ModeController = class {
|
|
|
2065
2328
|
set pendingModeRestart(val) {
|
|
2066
2329
|
this._pendingModeRestart = val;
|
|
2067
2330
|
}
|
|
2068
|
-
/**
|
|
2331
|
+
/**
|
|
2332
|
+
* Effective mode accounting for PM/task defaults.
|
|
2333
|
+
*
|
|
2334
|
+
* A `plan` runner is pinned to ONE mode for its whole life, whatever the
|
|
2335
|
+
* card's own agentMode later says. That pin is what makes the planner
|
|
2336
|
+
* read-only, non-build-capable, and prompted for planning — all three derive
|
|
2337
|
+
* from here — and it has to be immune to `applyServerMode`, which runs AFTER
|
|
2338
|
+
* boot and would otherwise stamp `auto` onto the planner of a plan-less auto
|
|
2339
|
+
* card, handing it the build prompt and `--dangerously-skip-permissions`
|
|
2340
|
+
* inside a session whose whole purpose is not to have them.
|
|
2341
|
+
*
|
|
2342
|
+
* There are TWO planner flavors, and which one this session is was decided at
|
|
2343
|
+
* boot (`_planFlavor`) — never re-read from the mutable `_mode`. A chat card
|
|
2344
|
+
* boots a plan session too (it is a Planner-session flavor, not a builder in
|
|
2345
|
+
* disguise), and it must keep `buildChatPrompt`, its file deliverables, and
|
|
2346
|
+
* the build-capable tool handler. Reading the flavor from `_mode` would put
|
|
2347
|
+
* that choice back under server control and re-open the exact hole the
|
|
2348
|
+
* paragraph above closes, in the other direction: a stamp of `chat` onto a
|
|
2349
|
+
* discovery planner would hand it build capability.
|
|
2350
|
+
*/
|
|
2069
2351
|
get effectiveMode() {
|
|
2352
|
+
if (this._runnerMode === "plan") return this._planFlavor;
|
|
2070
2353
|
if (this._mode) return this._mode;
|
|
2071
2354
|
if (this._runnerMode === "pm") {
|
|
2072
2355
|
return this._isAuto ? "auto" : "discovery";
|
|
@@ -2092,6 +2375,7 @@ var ModeController = class {
|
|
|
2092
2375
|
* still get the correct mode.
|
|
2093
2376
|
*/
|
|
2094
2377
|
applyServerMode(agentMode, isAuto) {
|
|
2378
|
+
this.latchPlanFlavor(agentMode);
|
|
2095
2379
|
if (agentMode) {
|
|
2096
2380
|
this._mode = agentMode;
|
|
2097
2381
|
this._isAuto = agentMode === "auto" || !!isAuto;
|
|
@@ -2112,6 +2396,7 @@ var ModeController = class {
|
|
|
2112
2396
|
this._mode = "review";
|
|
2113
2397
|
return this._mode;
|
|
2114
2398
|
}
|
|
2399
|
+
if (this._runnerMode === "plan") return this.effectiveMode;
|
|
2115
2400
|
if (this._mode === "auto" && this.canBypassPlanning(context)) {
|
|
2116
2401
|
this.transitionToBuilding(context);
|
|
2117
2402
|
return this._mode;
|
|
@@ -2203,7 +2488,7 @@ function defineTool(name, description, schema, handler, options) {
|
|
|
2203
2488
|
|
|
2204
2489
|
// src/harness/claude-code/index.ts
|
|
2205
2490
|
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
2206
|
-
import { z as
|
|
2491
|
+
import { z as z10 } from "zod";
|
|
2207
2492
|
var ClaudeCodeHarness = class {
|
|
2208
2493
|
/** The SDK stream is itself the structured-event source. */
|
|
2209
2494
|
emitsStructuredEvents = true;
|
|
@@ -2236,7 +2521,7 @@ var ClaudeCodeHarness = class {
|
|
|
2236
2521
|
}
|
|
2237
2522
|
);
|
|
2238
2523
|
if (t.strict) {
|
|
2239
|
-
sdkTool.inputSchema =
|
|
2524
|
+
sdkTool.inputSchema = z10.strictObject(t.schema);
|
|
2240
2525
|
}
|
|
2241
2526
|
return sdkTool;
|
|
2242
2527
|
});
|
|
@@ -3494,7 +3779,7 @@ var PtyOutputCoalescer = class {
|
|
|
3494
3779
|
|
|
3495
3780
|
// src/harness/pty/tool-server.ts
|
|
3496
3781
|
import { createServer as createServer2 } from "http";
|
|
3497
|
-
import { z as
|
|
3782
|
+
import { z as z11 } from "zod";
|
|
3498
3783
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
3499
3784
|
import { join as join3 } from "path";
|
|
3500
3785
|
import { randomBytes } from "crypto";
|
|
@@ -3551,7 +3836,7 @@ var PtyToolServer = class {
|
|
|
3551
3836
|
const mcp = new McpServer({ name: this.name, version: "1.0.0" });
|
|
3552
3837
|
const register = mcp.registerTool.bind(mcp);
|
|
3553
3838
|
for (const tool2 of this.tools) {
|
|
3554
|
-
const inputSchema = tool2.strict ?
|
|
3839
|
+
const inputSchema = tool2.strict ? z11.strictObject(tool2.schema) : tool2.schema;
|
|
3555
3840
|
register(
|
|
3556
3841
|
tool2.name,
|
|
3557
3842
|
{
|
|
@@ -6427,13 +6712,13 @@ Respond to them with post_to_chat (your turn output is NOT shown in chat).`
|
|
|
6427
6712
|
if (!context.plan?.trim()) {
|
|
6428
6713
|
parts.push(
|
|
6429
6714
|
`
|
|
6430
|
-
If this conversation turns into a development task: save a plan with
|
|
6715
|
+
If this conversation turns into a development task: save a plan with update_task first \u2014 that identifies the card and moves it to In Progress \u2014 then implement it and open a PR with mcp__conveyor__create_pull_request, like a normal build.`
|
|
6431
6716
|
);
|
|
6432
6717
|
return parts;
|
|
6433
6718
|
}
|
|
6434
6719
|
parts.push(
|
|
6435
6720
|
`
|
|
6436
|
-
This card has a saved plan, so it is a development task now. Implement
|
|
6721
|
+
This card has a saved plan, so it is a development task now. Implement it on the git branch "${context.githubBranch}", then run the \`/conveyor-build\` skill and follow it \u2014 the same workflow any build on this card would use, including how to verify before opening the PR.`
|
|
6437
6722
|
);
|
|
6438
6723
|
if (context.githubPRUrl) {
|
|
6439
6724
|
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
@@ -6569,7 +6854,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
6569
6854
|
``,
|
|
6570
6855
|
`1. Call list_subtasks to see the current state of all child tasks.`,
|
|
6571
6856
|
` The response includes PR info, agent assignment, **dependency info** (dependsOn array + allDependenciesMet flag), and the **packSlots** build-slot picture.`,
|
|
6572
|
-
` If list_subtasks returns NO children, this is a fresh parent card: break the work down now \u2014 explore the codebase, save a parent-level plan with
|
|
6857
|
+
` If list_subtasks returns NO children, this is a fresh parent card: break the work down now \u2014 explore the codebase, save a parent-level plan with update_task, then create child tasks with create_subtask, each with a detailed plan (file:line citations, verification steps) and dependsOn set for any child that blocks on another. Then fire the ready children and continue the loop.`,
|
|
6573
6858
|
``,
|
|
6574
6859
|
`2. Evaluate children by status and dependency readiness:`,
|
|
6575
6860
|
` - "ReviewPR": Review and merge its PR with approve_and_merge_pr. (Highest priority)`,
|
|
@@ -6622,6 +6907,73 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
6622
6907
|
parts.push(...PACK_RUNNER_FOOTER);
|
|
6623
6908
|
return parts.join("\n");
|
|
6624
6909
|
}
|
|
6910
|
+
function buildSinglePodPackPrompt(context, config, setupLog) {
|
|
6911
|
+
const packBranch = resolveMergedWorkBranch(context);
|
|
6912
|
+
const parts = [
|
|
6913
|
+
`You are an autonomous Pack Runner for the "${context.title}" pack, and you implement the work yourself.`,
|
|
6914
|
+
``,
|
|
6915
|
+
`Run the \`/conveyor-build\` skill and follow its PACK PATH. It is the source of truth for the loop \u2014 child selection, dependency order, the reviewer-of-record pass, the finale \u2014 and it carries the pod substitution this session runs under. Do not re-derive the workflow from memory.`,
|
|
6916
|
+
``,
|
|
6917
|
+
// Session-bound facts only. Everything the skill CAN know now lives there;
|
|
6918
|
+
// what stays is what is true of THIS pod and this card.
|
|
6919
|
+
`## This session`,
|
|
6920
|
+
`- You are ALREADY bound to this parent card. There is nothing to claim.`,
|
|
6921
|
+
`- One checkout, one branch: \`${packBranch}\`. Every child's work is commits on it.`,
|
|
6922
|
+
`- Children get NO branches and NO pull requests here. \`create_pull_request\` always opens the PR for the card you are bound to, so calling it mid-pack opens the PARENT's PR early and strands the remaining children. One PR, at the very end, for this parent.`,
|
|
6923
|
+
`- You move each child's status yourself: \`update_task(task_id: <child>, status: ...)\`. No per-child build fires, so a status you do not write is a board that silently lies. A child is "ReviewDev" once its commits are on \`${packBranch}\`.`,
|
|
6924
|
+
`- \`start_child_cloud_build\` and \`stop_child_build\` belong to the fan-out model and are not available to you \u2014 you are the implementer.`,
|
|
6925
|
+
`- Merge \`origin/dev\` into \`${packBranch}\` after each child, never rebase: the branch is shared with WIP refs and rewriting it breaks them.`,
|
|
6926
|
+
`- Nothing will wake you to start the next child. Go idle only when genuinely blocked on something external.`,
|
|
6927
|
+
``
|
|
6928
|
+
];
|
|
6929
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
6930
|
+
parts.push(...formatStoryPoints(context.storyPoints));
|
|
6931
|
+
}
|
|
6932
|
+
if (context.agents && context.agents.length > 0) {
|
|
6933
|
+
parts.push(...formatProjectAgents(context.agents));
|
|
6934
|
+
}
|
|
6935
|
+
if (setupLog.length > 0) {
|
|
6936
|
+
parts.push(``, `## Environment setup log`, "```", ...setupLog, "```");
|
|
6937
|
+
}
|
|
6938
|
+
if (context.agentInstructions) {
|
|
6939
|
+
parts.push(``, `## Agent Instructions`, context.agentInstructions);
|
|
6940
|
+
}
|
|
6941
|
+
if (config.instructions) {
|
|
6942
|
+
parts.push(``, `## Additional Instructions`, config.instructions);
|
|
6943
|
+
}
|
|
6944
|
+
parts.push(...PACK_RUNNER_FOOTER);
|
|
6945
|
+
return parts.join("\n");
|
|
6946
|
+
}
|
|
6947
|
+
function buildSinglePodPackInstructions(context, scenario) {
|
|
6948
|
+
const parts = [`
|
|
6949
|
+
## Instructions`];
|
|
6950
|
+
if (scenario === "fresh") {
|
|
6951
|
+
parts.push(
|
|
6952
|
+
`You are the Pack Runner for this task and its subtasks, and you implement them yourself.`,
|
|
6953
|
+
`Start now: call list_subtasks to see where the pack stands.`,
|
|
6954
|
+
`No children yet? Break the work down first \u2014 save a parent-level plan with update_task, then create_subtask each child with a detailed plan and dependsOn set where one blocks another.`,
|
|
6955
|
+
`Otherwise take the first "Open" child whose dependencies are met, claim it with update_task(task_id, status: "InProgress"), and implement it on this branch.`
|
|
6956
|
+
);
|
|
6957
|
+
} else if (scenario === "idle_relaunch") {
|
|
6958
|
+
parts.push(
|
|
6959
|
+
`You have been relaunched. Re-derive where the pack stands \u2014 call list_subtasks and check the branch, never rely on what you remember.`,
|
|
6960
|
+
`A child left "InProgress" is one YOU were implementing: check the working tree and the log for what already landed before redoing any of it.`,
|
|
6961
|
+
`Otherwise continue the loop: next ready child, or the finale if none remain.`
|
|
6962
|
+
);
|
|
6963
|
+
} else {
|
|
6964
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
6965
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
6966
|
+
parts.push(
|
|
6967
|
+
`You have been relaunched with new messages.`,
|
|
6968
|
+
`
|
|
6969
|
+
New messages since your last run:`,
|
|
6970
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
6971
|
+
`
|
|
6972
|
+
After addressing the feedback, resume: call list_subtasks and continue with the next ready child.`
|
|
6973
|
+
);
|
|
6974
|
+
}
|
|
6975
|
+
return parts;
|
|
6976
|
+
}
|
|
6625
6977
|
function buildPackRunnerInstructions(context, scenario) {
|
|
6626
6978
|
const parts = [`
|
|
6627
6979
|
## Instructions`];
|
|
@@ -6629,7 +6981,7 @@ function buildPackRunnerInstructions(context, scenario) {
|
|
|
6629
6981
|
parts.push(
|
|
6630
6982
|
`You are the Pack Runner for this task and its subtasks.`,
|
|
6631
6983
|
`Begin your autonomous loop immediately: call list_subtasks to assess the current state.`,
|
|
6632
|
-
`If there are no children yet, create them first \u2014 save a parent-level plan with
|
|
6984
|
+
`If there are no children yet, create them first \u2014 save a parent-level plan with update_task, then create child tasks with detailed plans and dependsOn set where one blocks on another. Then fire the ready ones.`,
|
|
6633
6985
|
`If any child is in "ReviewPR" status, review and merge its PR first.`,
|
|
6634
6986
|
`Then fire the next "Open" child task.`
|
|
6635
6987
|
);
|
|
@@ -6693,39 +7045,6 @@ function baseDiffCommand(baseBranch, flags) {
|
|
|
6693
7045
|
const suffix = flags ? ` ${flags}` : "";
|
|
6694
7046
|
return `git fetch origin ${base} -q && git diff $(git merge-base origin/${base} HEAD)..HEAD${suffix}`;
|
|
6695
7047
|
}
|
|
6696
|
-
function gateFailureModes() {
|
|
6697
|
-
return [
|
|
6698
|
-
`Reading a gate result correctly is what keeps this to ONE pass:`,
|
|
6699
|
-
`- Capture AND propagate the exit code (\`<gate> > <log> 2>&1; ec=$?; echo "EXIT:$ec" >> <log>; (exit $ec)\`) \u2014 piping a gate through \`tail\` masks it, and a bare trailing \`echo "EXIT:$?"\` makes the wrapper exit 0 over a failed gate. That \`EXIT:\` line, plus turbo's final \`Tasks:\` count, is the authority on pass/fail.`,
|
|
6700
|
-
`- A green gate that printed no per-suite summary (no \`Test Files\`/\`Tests\` line) is still green: \`turbo.json\` sets \`outputLogs: "errors-only"\`, so a PASSING run prints nothing per suite. Do NOT re-run a clean-exit gate to "see the counts" \u2014 if you genuinely need them, run one package's script directly (\`bun run --cwd <pkg> test\`).`,
|
|
6701
|
-
`- Exit 143, or \`singleton: stopping running '<label>'\` on stderr, means a second gate you started evicted this one (on a pod the heavy gates share one lock). 143 is never a pass \u2014 but an evicted run never finished, so re-running it alone is still your one pass, not a repeat. Run one gate at a time; \`bun run check\` is not lock-wrapped, so it is safe alongside a test run.`,
|
|
6702
|
-
`- Confirm every package your diff touches actually appears in the run. \`--affected\` can select nothing for a package you changed; when that happens run just that package's suite directly, e.g. \`bun run --cwd apps/api test:unit <changed test files>\` \u2014 not the whole gate again.`,
|
|
6703
|
-
...gateWaitProtocol()
|
|
6704
|
-
];
|
|
6705
|
-
}
|
|
6706
|
-
function gateWaitProtocol() {
|
|
6707
|
-
return [
|
|
6708
|
-
`How to wait for a gate \u2014 one gate, one wait, no polling:`,
|
|
6709
|
-
`- Launch it ONCE with \`run_in_background: true\`, then END YOUR TURN. The completion notification re-invokes you. Do not read the output file, \`tail\`/\`wc\`/\`pgrep\` it, or start a second watcher on a log another run already owns \u2014 a quiet gate is still running.`,
|
|
6710
|
-
`- Never wrap a gate in a foreground \`timeout\`: killing your own run at 590s produces exit 124/143 and ZERO information, and you then have to run it again unbounded. \`sleep N; <cmd>\` is hard-blocked for the same reason.`,
|
|
6711
|
-
`- Never relaunch a command you just killed without changing something. State cwd explicitly in the command (\`cd /workspaces/repo && \u2026\`, \`--root\`, \`git -C\`) rather than trusting the shell's working directory.`,
|
|
6712
|
-
`- The completion notification reports the WRAPPER's exit code. With the propagating idiom above, \`(exit $ec)\` makes that the gate's own code; a wrapper that ends on a bare \`echo\` reports 0 no matter what failed. Whenever the notification and the log disagree, the \`EXIT:\` line in the log is the authority.`,
|
|
6713
|
-
`- Scope heavy gates to what you changed (\`--filter=<pkg>\`). An unscoped \`check:affected\` can pull a large web typecheck into scope for a diff touching no web files and get OOM-killed (exit 137) after minutes, where the scoped run takes seconds.`,
|
|
6714
|
-
`- Merge the base BEFORE the gate pass, never after (see the Pre-PR Protocol). If that merge touched \`package.json\`/\`bun.lock\`, run \`bun install\` before starting the gate \u2014 a changed lockfile makes gates fail for reasons unrelated to your diff.`
|
|
6715
|
-
];
|
|
6716
|
-
}
|
|
6717
|
-
function ciTriageChecklist(baseBranch) {
|
|
6718
|
-
const base = baseBranch?.trim() || "dev";
|
|
6719
|
-
return [
|
|
6720
|
-
`Before treating a CI failure as yours, run these four checks first \u2014 most reported reds are not caused by the diff:`,
|
|
6721
|
-
`1. Is the failing run's head SHA still current? A red run against a superseded commit is stale.`,
|
|
6722
|
-
`2. Is the only \`##[error]\` a cancellation ("The operation was canceled", "The runner has received a shutdown signal")? That is a reclaimed/preempted runner, not a code failure \u2014 re-run it.`,
|
|
6723
|
-
`3. Is the failing file even in your diff? \`git diff origin/${base}...HEAD --stat -- <path>\` \u2014 empty output means you did not touch it. Check whether \`origin/${base}\` already fails the same way before investigating further.`,
|
|
6724
|
-
`4. Is \`origin/${base}\` itself green? A semantic merge conflict on the base branch fails every open PR at once.`,
|
|
6725
|
-
`Known environmental failures \u2014 recognize, do not re-diagnose: suites needing a real Elasticsearch fail without \`RC_TEST_ES_URL\` and cannot pass in a pod; exit 137 is a pod OOM, not a code error; \`gh\` returning \`HTTP 401: Bad credentials\` means the pod token aged out (~1h) \u2014 re-exporting it does not help, so use the Conveyor MCP CI-status tools instead of spending calls re-testing \`gh\`.`,
|
|
6726
|
-
`A Dependabot PR showing "no checks reported" is in \`action_required\` \u2014 a maintainer must approve the workflow run. That is not "CI did not run", and you cannot unblock it yourself.`
|
|
6727
|
-
];
|
|
6728
|
-
}
|
|
6729
7048
|
function formatFileSize(bytes) {
|
|
6730
7049
|
if (bytes === void 0) return "";
|
|
6731
7050
|
if (bytes < 1024) return `${bytes}B`;
|
|
@@ -6852,110 +7171,375 @@ function formatIncidents(incidents) {
|
|
|
6852
7171
|
return parts;
|
|
6853
7172
|
}
|
|
6854
7173
|
|
|
6855
|
-
// src/execution/
|
|
6856
|
-
var
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
".png",
|
|
6861
|
-
".jpg",
|
|
6862
|
-
".jpeg",
|
|
6863
|
-
".gif",
|
|
6864
|
-
".webp",
|
|
6865
|
-
".ico",
|
|
6866
|
-
".svg",
|
|
6867
|
-
".bmp",
|
|
6868
|
-
".mp3",
|
|
6869
|
-
".mp4",
|
|
6870
|
-
".wav",
|
|
6871
|
-
".avi",
|
|
6872
|
-
".mov",
|
|
6873
|
-
".pdf",
|
|
6874
|
-
".zip",
|
|
6875
|
-
".tar",
|
|
6876
|
-
".gz",
|
|
6877
|
-
".woff",
|
|
6878
|
-
".woff2",
|
|
6879
|
-
".ttf",
|
|
6880
|
-
".eot",
|
|
6881
|
-
".otf",
|
|
6882
|
-
".exe",
|
|
6883
|
-
".dll",
|
|
6884
|
-
".so",
|
|
6885
|
-
".dylib",
|
|
6886
|
-
".wasm"
|
|
6887
|
-
]);
|
|
6888
|
-
function isBinaryPath(filePath) {
|
|
6889
|
-
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
6890
|
-
return BINARY_EXTENSIONS.has(ext);
|
|
7174
|
+
// src/execution/mode-prompt.ts
|
|
7175
|
+
var SP_DESC_MAX_CHARS = 80;
|
|
7176
|
+
function truncateDescription(desc, maxChars) {
|
|
7177
|
+
if (desc.length <= maxChars) return desc;
|
|
7178
|
+
return desc.slice(0, maxChars) + "\u2026";
|
|
6891
7179
|
}
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
let frontmatter = "";
|
|
6899
|
-
if (raw.startsWith("---")) {
|
|
6900
|
-
const end = raw.indexOf("\n---", 3);
|
|
6901
|
-
if (end !== -1) {
|
|
6902
|
-
frontmatter = raw.slice(3, end);
|
|
6903
|
-
const afterClose = raw.indexOf("\n", end + 1);
|
|
6904
|
-
body = afterClose === -1 ? "" : raw.slice(afterClose + 1);
|
|
6905
|
-
}
|
|
6906
|
-
}
|
|
6907
|
-
const fmDescription = frontmatter.split("\n").map((l) => l.trim()).find((l) => /^(description|title):/i.test(l));
|
|
6908
|
-
if (fmDescription) {
|
|
6909
|
-
const value = fmDescription.slice(fmDescription.indexOf(":") + 1).trim().replace(/^["']|["']$/g, "");
|
|
6910
|
-
if (value) return truncateSummary(value);
|
|
6911
|
-
}
|
|
6912
|
-
for (const rawLine of body.split("\n")) {
|
|
6913
|
-
const line = rawLine.trim();
|
|
6914
|
-
if (!line) continue;
|
|
6915
|
-
const cleaned = line.replace(/^#+\s*/, "").replace(/^[-*]\s+/, "").trim();
|
|
6916
|
-
if (cleaned) return truncateSummary(cleaned);
|
|
7180
|
+
function formatTagWithContextPaths(tag) {
|
|
7181
|
+
const desc = tag.description ? ` \u2014 ${tag.description}` : "";
|
|
7182
|
+
const lines = [`- Name: "${tag.name}"${desc}`];
|
|
7183
|
+
for (const link of tag.contextPaths ?? []) {
|
|
7184
|
+
const label = link.label ? ` (${link.label})` : "";
|
|
7185
|
+
lines.push(` \u2192 ${link.type}: ${link.path}${label}`);
|
|
6917
7186
|
}
|
|
6918
|
-
return
|
|
7187
|
+
return lines;
|
|
6919
7188
|
}
|
|
6920
|
-
function
|
|
6921
|
-
const
|
|
6922
|
-
|
|
7189
|
+
function buildEstimateReassessmentLines(context) {
|
|
7190
|
+
const currentSp = context.taskStoryPointValue ?? "unset";
|
|
7191
|
+
const currentRisk = context.taskRiskLevel ?? "unset";
|
|
7192
|
+
return [
|
|
7193
|
+
`Story points and risk are LIVING estimates, not one-time labels. Reassess them at every phase \u2014 planning, building, review \u2014 against your current understanding, and adjust in EITHER direction: work that looks big early is often small once scoped, and vice versa. Lowering an inflated estimate is as valuable as raising an underestimate; a stale value is worse than a changed one.`,
|
|
7194
|
+
`Current values: story points ${currentSp}, risk ${currentRisk}.`,
|
|
7195
|
+
``,
|
|
7196
|
+
`Risk levels (how much important surface the change touches):`,
|
|
7197
|
+
`- critical: foundational surface \u2014 auth, billing, data integrity, migrations`,
|
|
7198
|
+
`- high: important surface with broad blast radius`,
|
|
7199
|
+
`- medium: moderate, contained surface area`,
|
|
7200
|
+
`- low: small or isolated change`
|
|
7201
|
+
];
|
|
6923
7202
|
}
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
7203
|
+
function buildPropertyInstructions(context, runnerMode) {
|
|
7204
|
+
const isTask = runnerMode === "task";
|
|
7205
|
+
const parts = [];
|
|
7206
|
+
parts.push(
|
|
7207
|
+
``,
|
|
7208
|
+
`### Proactive Property Management`,
|
|
7209
|
+
`As you work this task, proactively keep task properties accurate:`,
|
|
7210
|
+
`- Use update_task_properties to set any combination of: title, story points, risk, and tags`,
|
|
7211
|
+
`- You can update all properties at once or just one at a time as needed`,
|
|
7212
|
+
`- Icons are assigned automatically during identification \u2014 do not set icons manually`,
|
|
7213
|
+
``,
|
|
7214
|
+
...buildEstimateReassessmentLines(context),
|
|
7215
|
+
``,
|
|
7216
|
+
`Don't wait for the user to ask \u2014 keep these accurate as the work takes shape.`,
|
|
7217
|
+
`If scope changes materially at any point, update the properties to match.`
|
|
7218
|
+
);
|
|
7219
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
7220
|
+
parts.push(``, `Available story point tiers:`);
|
|
7221
|
+
for (const sp of context.storyPoints) {
|
|
7222
|
+
const desc = sp.description ? ` \u2014 ${truncateDescription(sp.description, SP_DESC_MAX_CHARS)}` : "";
|
|
7223
|
+
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
6933
7224
|
}
|
|
6934
|
-
const raw = await readWorkspaceFile(filePath);
|
|
6935
|
-
fileReadCount++;
|
|
6936
|
-
const summary = deriveSummary(raw.slice(0, SUMMARY_SCAN_CHARS));
|
|
6937
|
-
fileSummaryCache.set(filePath, { mtimeMs, summary });
|
|
6938
|
-
return summary;
|
|
6939
|
-
} catch {
|
|
6940
|
-
return null;
|
|
6941
7225
|
}
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
const
|
|
6946
|
-
if (
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
if (cached && cached.mtimeMs === mtimeMs) {
|
|
6950
|
-
return cached.listing;
|
|
7226
|
+
if (context.projectTags && context.projectTags.length > 0) {
|
|
7227
|
+
const assignedIds = new Set(context.taskTagIds ?? []);
|
|
7228
|
+
const assigned = context.projectTags.filter((t) => assignedIds.has(t.id));
|
|
7229
|
+
const unassigned = context.projectTags.filter((t) => !assignedIds.has(t.id));
|
|
7230
|
+
if (assigned.length > 0) {
|
|
7231
|
+
parts.push(``, `Assigned tags:`);
|
|
7232
|
+
for (const tag of assigned) parts.push(...formatTagWithContextPaths(tag));
|
|
6951
7233
|
}
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
7234
|
+
if (!isTask && unassigned.length > 0) {
|
|
7235
|
+
parts.push(``, `Available project tags:`);
|
|
7236
|
+
for (const tag of unassigned) parts.push(...formatTagWithContextPaths(tag));
|
|
7237
|
+
}
|
|
7238
|
+
parts.push(
|
|
7239
|
+
``,
|
|
7240
|
+
`Tags are the project glossary. call get_tag("<name>") for a term's full spec (overview, linked files, hierarchy) whenever a tag is assigned to this card or mentioned (@[tag:id]) in chat/plans. To mention a tag yourself, write @[tag:<name>] with the tag's exact name \u2014 the server rewrites it to the id token when it stores the text, so you never need the id. An unknown name is left as plain text. When your work changes how a tagged system behaves, update that tag's overview via update_tag with a short reason \u2014 your card is stamped into the revision history.`
|
|
7241
|
+
);
|
|
7242
|
+
}
|
|
7243
|
+
return parts;
|
|
7244
|
+
}
|
|
7245
|
+
function buildPlanRevisionSection() {
|
|
7246
|
+
return [
|
|
7247
|
+
``,
|
|
7248
|
+
`### The plan can change while you sleep`,
|
|
7249
|
+
`If a turn opens with "The plan changed since this build was dispatched", honor it BEFORE your next Write or Edit:`,
|
|
7250
|
+
`1. Call get_current_plan and read the current plan in full.`,
|
|
7251
|
+
`2. Compare it against the plan you were launched with \u2014 the current plan wins.`,
|
|
7252
|
+
`3. Drop or redo whatever the revision supersedes, and say so with post_to_chat if it invalidates work you already committed.`,
|
|
7253
|
+
`Building on a superseded plan is the most expensive mistake a resumed card can make.`
|
|
7254
|
+
];
|
|
7255
|
+
}
|
|
7256
|
+
function buildSkillInvocation(skill) {
|
|
7257
|
+
return [
|
|
7258
|
+
`Run the \`${skill}\` skill and follow it. It is the source of truth for how this work is done \u2014 do not re-derive the workflow from memory.`,
|
|
7259
|
+
`Two things it cannot know, because they are true of this session rather than of the workflow:`,
|
|
7260
|
+
`- You are ALREADY bound to this card. There is nothing to resolve or claim, and no card argument to ask for \u2014 the task context above is it.`,
|
|
7261
|
+
`- Your plan belongs on the card (\`update_task\`), and your PR goes through \`create_pull_request\`. Do not hand-edit card status to fake a transition.`
|
|
7262
|
+
];
|
|
7263
|
+
}
|
|
7264
|
+
function buildEnforcedToolContracts() {
|
|
7265
|
+
return [
|
|
7266
|
+
``,
|
|
7267
|
+
`### Contracts this environment enforces`,
|
|
7268
|
+
`- Full coding access (read, write, edit, bash, git). Destructive operations are BLOCKED at the tool layer: use \`--force-with-lease\`, never \`--force\`; no \`git reset --hard\`, no \`rm -rf /\`.`,
|
|
7269
|
+
`- A tool error or "MCP server unavailable" is almost always a transient socket reconnect: RETRY the same call. Do not stop work, go idle, or treat it as a reason to end your turn.`,
|
|
7270
|
+
`- Repeating an identical call is interrupted every 4th time. That is a loop breaker, not a denial of the work \u2014 change something about the call rather than repeating it.`
|
|
7271
|
+
];
|
|
7272
|
+
}
|
|
7273
|
+
function buildHarnessContracts(context) {
|
|
7274
|
+
return [
|
|
7275
|
+
...buildEnforcedToolContracts(),
|
|
7276
|
+
...buildPlanRevisionSection(),
|
|
7277
|
+
...context?.baseBranch ? [
|
|
7278
|
+
``,
|
|
7279
|
+
`This card's base branch is \`${context.baseBranch}\` \u2014 pass it explicitly when you open the PR.`
|
|
7280
|
+
] : []
|
|
7281
|
+
];
|
|
7282
|
+
}
|
|
7283
|
+
function buildDiscoveryPrompt(context, runnerMode) {
|
|
7284
|
+
const parts = [
|
|
7285
|
+
`
|
|
7286
|
+
## Mode: Discovery`,
|
|
7287
|
+
`You are in Discovery mode \u2014 planning and scoping this card WITH the team.`,
|
|
7288
|
+
...buildSkillInvocation("/conveyor-plan"),
|
|
7289
|
+
...context?.isParentTask ? [
|
|
7290
|
+
`- This card is a pack parent: the deliverable is child cards with real plans and \`dependsOn\` edges, not an implementation plan for yourself.`
|
|
7291
|
+
] : [],
|
|
7292
|
+
``,
|
|
7293
|
+
`### Contracts this environment enforces`,
|
|
7294
|
+
`- 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).`,
|
|
7295
|
+
`- A plan file on disk is NOT the plan. Only \`update_task\` saves it to the card.`,
|
|
7296
|
+
`- **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.`,
|
|
7297
|
+
`- ExitPlanMode does NOT start building. It parks this session for human review; the team decides when to build.`,
|
|
7298
|
+
...runnerMode === "plan" ? [
|
|
7299
|
+
`- You are the Planner half of this card's lifecycle. When the team presses Build, a separate **Builder** session starts on THIS pod and takes over \u2014 you never become it. So the plan has to stand on its own: the Builder inherits this repo checkout, but none of your reasoning.`
|
|
7300
|
+
] : [],
|
|
7301
|
+
`- A tool error or "MCP server unavailable" is almost always a transient socket reconnect: RETRY the same call rather than ending your turn.`
|
|
7302
|
+
];
|
|
7303
|
+
if (context) parts.push(...buildPropertyInstructions(context, runnerMode));
|
|
7304
|
+
return parts.join("\n");
|
|
7305
|
+
}
|
|
7306
|
+
function buildAutoPrompt(context, runnerMode) {
|
|
7307
|
+
const parts = [
|
|
7308
|
+
`
|
|
7309
|
+
## Mode: Auto`,
|
|
7310
|
+
`You are in Auto mode \u2014 plan this card, then build it, without stopping for approval.`,
|
|
7311
|
+
`Run \`/conveyor-plan\` FIRST to produce the plan and save it to the card, then run \`/conveyor-build\` to implement it. Both are sources of truth; do not re-derive them.`,
|
|
7312
|
+
...buildSkillInvocation("/conveyor-build"),
|
|
7313
|
+
``,
|
|
7314
|
+
`### What "auto" changes`,
|
|
7315
|
+
`- There is no plan-approval step and no read-only phase. The plan is a RECORD for the team, not a gate \u2014 save it, then immediately write the code. A card whose only output is a plan (or a plan-only PR) has not been built.`,
|
|
7316
|
+
`- Decide independently. Escalate only when genuinely blocked: ambiguous requirements, missing access, conflicting instructions. Everything else is yours to call.`,
|
|
7317
|
+
`- Skip \`/conveyor-plan\` only if the card already carries a plan you are not materially diverging from.`,
|
|
7318
|
+
...buildHarnessContracts(context)
|
|
7319
|
+
];
|
|
7320
|
+
if (context) parts.push(...buildPropertyInstructions(context, runnerMode));
|
|
7321
|
+
return parts.join("\n");
|
|
7322
|
+
}
|
|
7323
|
+
function buildBuildingPrompt(context) {
|
|
7324
|
+
const parts = [
|
|
7325
|
+
`
|
|
7326
|
+
## Mode: Building`,
|
|
7327
|
+
`You are in Building mode \u2014 executing this card's plan.`,
|
|
7328
|
+
...buildSkillInvocation("/conveyor-build"),
|
|
7329
|
+
...context?.isParentTask ? [`- This card is a pack parent. \`/conveyor-build\` routes to its pack path; follow that.`] : [],
|
|
7330
|
+
...buildHarnessContracts(context)
|
|
7331
|
+
];
|
|
7332
|
+
if (context) parts.push(...buildPropertyInstructions(context));
|
|
7333
|
+
return parts.join("\n");
|
|
7334
|
+
}
|
|
7335
|
+
function buildModePrompt(agentMode, context, runnerMode) {
|
|
7336
|
+
switch (agentMode) {
|
|
7337
|
+
case "discovery":
|
|
7338
|
+
return buildDiscoveryPrompt(context, runnerMode);
|
|
7339
|
+
case "building":
|
|
7340
|
+
return buildBuildingPrompt(context);
|
|
7341
|
+
case "review":
|
|
7342
|
+
return buildReviewPrompt(context);
|
|
7343
|
+
case "auto":
|
|
7344
|
+
return buildAutoPrompt(context, runnerMode);
|
|
7345
|
+
case "chat":
|
|
7346
|
+
return buildChatPrompt(context);
|
|
7347
|
+
default:
|
|
7348
|
+
return null;
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
function buildChatPrompt(context) {
|
|
7352
|
+
const base = context?.baseBranch?.trim() || "dev";
|
|
7353
|
+
return [
|
|
7354
|
+
`
|
|
7355
|
+
## Mode: Chat`,
|
|
7356
|
+
`You are in Chat mode \u2014 a conversational assistant working directly with the user on this card.`,
|
|
7357
|
+
`- Respond conversationally to the user in chat. Ask clarifying questions when useful; this is a back-and-forth, not an autonomous build.`,
|
|
7358
|
+
`- You have full read/write access to the workspace and can run non-destructive shell commands, so you CAN create files (notes, scripts, docs, data, diagrams, etc.) when they help the user.`,
|
|
7359
|
+
`- This card starts Unidentified. That is correct \u2014 it stays a conversation until the work becomes a development task (see below).`,
|
|
7360
|
+
``,
|
|
7361
|
+
`### Deliverables \u2014 attach files to the card`,
|
|
7362
|
+
`- When you create a file the user should keep, attach it to the card with the \`upload_attachment\` tool so it shows up on the card. Mention in chat what you attached.`,
|
|
7363
|
+
`- \`upload_attachment\` accepts any file type (markdown, PDF, HTML, text, data, images) up to 25MB \u2014 this is how docs are delivered. Do NOT publish deliverables as a Claude artifact or any other off-platform link; the card is the system of record.`,
|
|
7364
|
+
`- For conversational work, attachments are the deliverable. Do NOT open a PR to hand over a document or an answer.`,
|
|
7365
|
+
``,
|
|
7366
|
+
`### Turning into a development task`,
|
|
7367
|
+
`- If the conversation becomes a request to change the code, escalate the card first: save a plan with \`update_task\`. That first plan identifies the card and moves it to In Progress.`,
|
|
7368
|
+
`- The plan is also the gate on your PR path: \`create_pull_request\`, \`git push\` and \`gh pr\` are DENIED until a plan is saved, and allowed afterwards. Do not try to work around a denial \u2014 save the plan.`,
|
|
7369
|
+
`- Once the plan is saved, it IS a normal build: run the \`/conveyor-build\` skill and follow it, including how to sync \`${base}\` and verify before opening the PR. Do not re-derive that workflow here.`,
|
|
7370
|
+
`- Keep talking to the user while you build \u2014 this is still their card.`,
|
|
7371
|
+
``,
|
|
7372
|
+
`### Finishing the conversation`,
|
|
7373
|
+
`- When the user indicates they are done (or explicitly asks to wrap up / close the card), call \`force_update_task_status\` with status \`"Complete"\`. It works from either status, so a card still sitting Unidentified can be completed the same way as one that reached In Progress.`,
|
|
7374
|
+
`- If you opened a PR, do NOT complete the card \u2014 the PR review flow takes it from there.`,
|
|
7375
|
+
`- If the user is still engaged, keep helping \u2014 only complete the card once the interaction has concluded.`,
|
|
7376
|
+
`- Do not complete the card while you still owe the user a response or an attachment.`
|
|
7377
|
+
].join("\n");
|
|
7378
|
+
}
|
|
7379
|
+
function buildReviewTagSection(context) {
|
|
7380
|
+
const assignedIds = new Set(context?.taskTagIds ?? []);
|
|
7381
|
+
const assigned = (context?.projectTags ?? []).filter((t) => assignedIds.has(t.id));
|
|
7382
|
+
if (assigned.length === 0) return [];
|
|
7383
|
+
const parts = [
|
|
7384
|
+
`### Card Tags & Domain Context`,
|
|
7385
|
+
`This card carries these project glossary tags:`
|
|
7386
|
+
];
|
|
7387
|
+
for (const tag of assigned) parts.push(...formatTagWithContextPaths(tag));
|
|
7388
|
+
parts.push(
|
|
7389
|
+
``,
|
|
7390
|
+
`A tag's rules and overview define the conventions the code under review must follow. Use them when you judge Pattern Consistency:`,
|
|
7391
|
+
`- Read the tag entries that match the files in this diff. The "Reference Guides" section of the task brief lists the same tags with a one-line summary of each linked doc.`,
|
|
7392
|
+
`- Call get_tag("<name>") for a term's full spec (overview, linked files, hierarchy) when the linked docs are not enough.`,
|
|
7393
|
+
`- A change that contradicts a tagged system's documented conventions is a review finding, even when it reads fine on its own.`,
|
|
7394
|
+
`- Skip the tags this diff does not touch \u2014 do not read them all up front.`,
|
|
7395
|
+
`- If the diff changes how a tagged system behaves and the tag's overview is now wrong, say so in your review.`,
|
|
7396
|
+
``
|
|
7397
|
+
);
|
|
7398
|
+
return parts;
|
|
7399
|
+
}
|
|
7400
|
+
function buildReviewPrompt(context) {
|
|
7401
|
+
if (context?.isParentTask) return buildParentReviewPrompt();
|
|
7402
|
+
return [
|
|
7403
|
+
`
|
|
7404
|
+
## Mode: Review`,
|
|
7405
|
+
...buildSkillInvocation("/conveyor-review"),
|
|
7406
|
+
``,
|
|
7407
|
+
// Everything below is harness-bound: the skill cannot know this session's
|
|
7408
|
+
// resolved branch, and the verdict tool names differ per surface (the skill
|
|
7409
|
+
// documents both pairs; this names the one that exists here).
|
|
7410
|
+
`### This session`,
|
|
7411
|
+
`- The diff under review: \`${baseDiffCommand(context?.baseBranch)}\``,
|
|
7412
|
+
`- Your verdict tools on this pod are \`approve_code_review\` and \`request_code_changes\`. The local conveyor-mcp pair does not exist here.`,
|
|
7413
|
+
...buildEnforcedToolContracts(),
|
|
7414
|
+
...buildReviewTagSection(context)
|
|
7415
|
+
].join("\n");
|
|
7416
|
+
}
|
|
7417
|
+
function buildParentReviewPrompt() {
|
|
7418
|
+
return [
|
|
7419
|
+
`
|
|
7420
|
+
## Mode: Review`,
|
|
7421
|
+
`### Parent Task Review`,
|
|
7422
|
+
`You are reviewing and coordinating child tasks.`,
|
|
7423
|
+
`- Use \`list_subtasks\` to see current child task state and progress.`,
|
|
7424
|
+
`- For children in ReviewPR status: review their code quality and merge with \`approve_and_merge_pr\`.`,
|
|
7425
|
+
`- For children with failing CI: check with \`get_execution_logs(childTaskId)\` and escalate if stuck.`,
|
|
7426
|
+
`- Fire next child builds with \`start_child_cloud_build\` when ready.`,
|
|
7427
|
+
`- Create follow-up tasks for issues discovered during review.`,
|
|
7428
|
+
``,
|
|
7429
|
+
`### Coordination Workflow`,
|
|
7430
|
+
`1. Check child task statuses with \`list_subtasks\``,
|
|
7431
|
+
`2. Review completed children \u2014 check PRs, run tests if needed`,
|
|
7432
|
+
`3. Approve and merge passing PRs`,
|
|
7433
|
+
`4. Fire builds for children that are ready`,
|
|
7434
|
+
`5. Create follow-up tasks for anything out of scope`,
|
|
7435
|
+
`6. As children complete, correct their story points with update_subtask (storyPointValue) when the actual work diverged from the estimate \u2014 either direction`
|
|
7436
|
+
].join("\n");
|
|
7437
|
+
}
|
|
7438
|
+
|
|
7439
|
+
// src/execution/tag-context-resolver.ts
|
|
7440
|
+
var TYPE_PRIORITY = { rule: 0, doc: 1, file: 2, folder: 3 };
|
|
7441
|
+
var SUMMARY_SCAN_CHARS = 4e3;
|
|
7442
|
+
var SUMMARY_MAX_CHARS = 160;
|
|
7443
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7444
|
+
".png",
|
|
7445
|
+
".jpg",
|
|
7446
|
+
".jpeg",
|
|
7447
|
+
".gif",
|
|
7448
|
+
".webp",
|
|
7449
|
+
".ico",
|
|
7450
|
+
".svg",
|
|
7451
|
+
".bmp",
|
|
7452
|
+
".mp3",
|
|
7453
|
+
".mp4",
|
|
7454
|
+
".wav",
|
|
7455
|
+
".avi",
|
|
7456
|
+
".mov",
|
|
7457
|
+
".pdf",
|
|
7458
|
+
".zip",
|
|
7459
|
+
".tar",
|
|
7460
|
+
".gz",
|
|
7461
|
+
".woff",
|
|
7462
|
+
".woff2",
|
|
7463
|
+
".ttf",
|
|
7464
|
+
".eot",
|
|
7465
|
+
".otf",
|
|
7466
|
+
".exe",
|
|
7467
|
+
".dll",
|
|
7468
|
+
".so",
|
|
7469
|
+
".dylib",
|
|
7470
|
+
".wasm"
|
|
7471
|
+
]);
|
|
7472
|
+
function isBinaryPath(filePath) {
|
|
7473
|
+
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
7474
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
7475
|
+
}
|
|
7476
|
+
var fileSummaryCache = /* @__PURE__ */ new Map();
|
|
7477
|
+
var folderListingCache = /* @__PURE__ */ new Map();
|
|
7478
|
+
var fileReadCount = 0;
|
|
7479
|
+
var folderReadCount = 0;
|
|
7480
|
+
function deriveSummary(raw) {
|
|
7481
|
+
let body = raw;
|
|
7482
|
+
let frontmatter = "";
|
|
7483
|
+
if (raw.startsWith("---")) {
|
|
7484
|
+
const end = raw.indexOf("\n---", 3);
|
|
7485
|
+
if (end !== -1) {
|
|
7486
|
+
frontmatter = raw.slice(3, end);
|
|
7487
|
+
const afterClose = raw.indexOf("\n", end + 1);
|
|
7488
|
+
body = afterClose === -1 ? "" : raw.slice(afterClose + 1);
|
|
7489
|
+
}
|
|
7490
|
+
}
|
|
7491
|
+
const fmDescription = frontmatter.split("\n").map((l) => l.trim()).find((l) => /^(description|title):/i.test(l));
|
|
7492
|
+
if (fmDescription) {
|
|
7493
|
+
const value = fmDescription.slice(fmDescription.indexOf(":") + 1).trim().replace(/^["']|["']$/g, "");
|
|
7494
|
+
if (value) return truncateSummary(value);
|
|
7495
|
+
}
|
|
7496
|
+
for (const rawLine of body.split("\n")) {
|
|
7497
|
+
const line = rawLine.trim();
|
|
7498
|
+
if (!line) continue;
|
|
7499
|
+
const cleaned = line.replace(/^#+\s*/, "").replace(/^[-*]\s+/, "").trim();
|
|
7500
|
+
if (cleaned) return truncateSummary(cleaned);
|
|
7501
|
+
}
|
|
7502
|
+
return null;
|
|
7503
|
+
}
|
|
7504
|
+
function truncateSummary(text) {
|
|
7505
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
7506
|
+
return collapsed.length > SUMMARY_MAX_CHARS ? collapsed.slice(0, SUMMARY_MAX_CHARS - 1).trimEnd() + "\u2026" : collapsed;
|
|
7507
|
+
}
|
|
7508
|
+
async function readFileSummary(filePath) {
|
|
7509
|
+
try {
|
|
7510
|
+
if (isBinaryPath(filePath)) return null;
|
|
7511
|
+
const st = await statWorkspacePath(filePath);
|
|
7512
|
+
if (!st.exists) return null;
|
|
7513
|
+
const mtimeMs = st.mtimeMs;
|
|
7514
|
+
const cached = fileSummaryCache.get(filePath);
|
|
7515
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
7516
|
+
return cached.summary;
|
|
7517
|
+
}
|
|
7518
|
+
const raw = await readWorkspaceFile(filePath);
|
|
7519
|
+
fileReadCount++;
|
|
7520
|
+
const summary = deriveSummary(raw.slice(0, SUMMARY_SCAN_CHARS));
|
|
7521
|
+
fileSummaryCache.set(filePath, { mtimeMs, summary });
|
|
7522
|
+
return summary;
|
|
7523
|
+
} catch {
|
|
7524
|
+
return null;
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
async function readFolderListing(folderPath) {
|
|
7528
|
+
try {
|
|
7529
|
+
const st = await statWorkspacePath(folderPath);
|
|
7530
|
+
if (!st.exists) return null;
|
|
7531
|
+
const mtimeMs = st.mtimeMs;
|
|
7532
|
+
const cached = folderListingCache.get(folderPath);
|
|
7533
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
7534
|
+
return cached.listing;
|
|
7535
|
+
}
|
|
7536
|
+
const entries = await readWorkspaceDir(folderPath);
|
|
7537
|
+
folderReadCount++;
|
|
7538
|
+
const listing = `Files: ${entries.join(", ")}`;
|
|
7539
|
+
folderListingCache.set(folderPath, { mtimeMs, listing });
|
|
7540
|
+
return listing;
|
|
7541
|
+
} catch {
|
|
7542
|
+
return null;
|
|
6959
7543
|
}
|
|
6960
7544
|
}
|
|
6961
7545
|
async function resolveEntry(entry) {
|
|
@@ -7182,7 +7766,7 @@ function buildPmAutoPlanningRelaunchParts() {
|
|
|
7182
7766
|
return [
|
|
7183
7767
|
`
|
|
7184
7768
|
You are in auto mode. Continue building autonomously.`,
|
|
7185
|
-
`No plan is saved on this card yet \u2014 save a concise plan with
|
|
7769
|
+
`No plan is saved on this card yet \u2014 save a concise plan with update_task before writing further code; never pause or wait for approval.`,
|
|
7186
7770
|
`Do NOT wait for team input \u2014 proceed autonomously.`
|
|
7187
7771
|
];
|
|
7188
7772
|
}
|
|
@@ -7210,491 +7794,6 @@ function buildPmRelaunchParts(context, lastAgentIdx, isAuto, agentMode) {
|
|
|
7210
7794
|
// src/execution/system-prompt.ts
|
|
7211
7795
|
import { readFileSync } from "fs";
|
|
7212
7796
|
import { join as join11 } from "path";
|
|
7213
|
-
|
|
7214
|
-
// src/execution/mode-prompt.ts
|
|
7215
|
-
var SP_DESC_MAX_CHARS = 80;
|
|
7216
|
-
function truncateDescription(desc, maxChars) {
|
|
7217
|
-
if (desc.length <= maxChars) return desc;
|
|
7218
|
-
return desc.slice(0, maxChars) + "\u2026";
|
|
7219
|
-
}
|
|
7220
|
-
function formatTagWithContextPaths(tag) {
|
|
7221
|
-
const desc = tag.description ? ` \u2014 ${tag.description}` : "";
|
|
7222
|
-
const lines = [`- Name: "${tag.name}"${desc}`];
|
|
7223
|
-
for (const link of tag.contextPaths ?? []) {
|
|
7224
|
-
const label = link.label ? ` (${link.label})` : "";
|
|
7225
|
-
lines.push(` \u2192 ${link.type}: ${link.path}${label}`);
|
|
7226
|
-
}
|
|
7227
|
-
return lines;
|
|
7228
|
-
}
|
|
7229
|
-
function buildEstimateReassessmentLines(context) {
|
|
7230
|
-
const currentSp = context.taskStoryPointValue ?? "unset";
|
|
7231
|
-
const currentRisk = context.taskRiskLevel ?? "unset";
|
|
7232
|
-
return [
|
|
7233
|
-
`Story points and risk are LIVING estimates, not one-time labels. Reassess them at every phase \u2014 planning, building, review \u2014 against your current understanding, and adjust in EITHER direction: work that looks big early is often small once scoped, and vice versa. Lowering an inflated estimate is as valuable as raising an underestimate; a stale value is worse than a changed one.`,
|
|
7234
|
-
`Current values: story points ${currentSp}, risk ${currentRisk}.`,
|
|
7235
|
-
``,
|
|
7236
|
-
`Risk levels (how much important surface the change touches):`,
|
|
7237
|
-
`- critical: foundational surface \u2014 auth, billing, data integrity, migrations`,
|
|
7238
|
-
`- high: important surface with broad blast radius`,
|
|
7239
|
-
`- medium: moderate, contained surface area`,
|
|
7240
|
-
`- low: small or isolated change`
|
|
7241
|
-
];
|
|
7242
|
-
}
|
|
7243
|
-
function buildPropertyInstructions(context, runnerMode) {
|
|
7244
|
-
const isTask = runnerMode === "task";
|
|
7245
|
-
const parts = [];
|
|
7246
|
-
parts.push(
|
|
7247
|
-
``,
|
|
7248
|
-
`### Proactive Property Management`,
|
|
7249
|
-
`As you work this task, proactively keep task properties accurate:`,
|
|
7250
|
-
`- Use update_task_properties to set any combination of: title, story points, risk, and tags`,
|
|
7251
|
-
`- You can update all properties at once or just one at a time as needed`,
|
|
7252
|
-
`- Icons are assigned automatically during identification \u2014 do not set icons manually`,
|
|
7253
|
-
``,
|
|
7254
|
-
...buildEstimateReassessmentLines(context),
|
|
7255
|
-
``,
|
|
7256
|
-
`Don't wait for the user to ask \u2014 keep these accurate as the work takes shape.`,
|
|
7257
|
-
`If scope changes materially at any point, update the properties to match.`
|
|
7258
|
-
);
|
|
7259
|
-
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
7260
|
-
parts.push(``, `Available story point tiers:`);
|
|
7261
|
-
for (const sp of context.storyPoints) {
|
|
7262
|
-
const desc = sp.description ? ` \u2014 ${truncateDescription(sp.description, SP_DESC_MAX_CHARS)}` : "";
|
|
7263
|
-
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
7264
|
-
}
|
|
7265
|
-
}
|
|
7266
|
-
if (context.projectTags && context.projectTags.length > 0) {
|
|
7267
|
-
const assignedIds = new Set(context.taskTagIds ?? []);
|
|
7268
|
-
const assigned = context.projectTags.filter((t) => assignedIds.has(t.id));
|
|
7269
|
-
const unassigned = context.projectTags.filter((t) => !assignedIds.has(t.id));
|
|
7270
|
-
if (assigned.length > 0) {
|
|
7271
|
-
parts.push(``, `Assigned tags:`);
|
|
7272
|
-
for (const tag of assigned) parts.push(...formatTagWithContextPaths(tag));
|
|
7273
|
-
}
|
|
7274
|
-
if (!isTask && unassigned.length > 0) {
|
|
7275
|
-
parts.push(``, `Available project tags:`);
|
|
7276
|
-
for (const tag of unassigned) parts.push(...formatTagWithContextPaths(tag));
|
|
7277
|
-
}
|
|
7278
|
-
parts.push(
|
|
7279
|
-
``,
|
|
7280
|
-
`Tags are the project glossary. call get_tag("<name>") for a term's full spec (overview, linked files, hierarchy) whenever a tag is assigned to this card or mentioned (@[tag:id]) in chat/plans. To mention a tag yourself, write @[tag:<name>] with the tag's exact name \u2014 the server rewrites it to the id token when it stores the text, so you never need the id. An unknown name is left as plain text. When your work changes how a tagged system behaves, update that tag's overview via update_tag with a short reason \u2014 your card is stamped into the revision history.`
|
|
7281
|
-
);
|
|
7282
|
-
}
|
|
7283
|
-
return parts;
|
|
7284
|
-
}
|
|
7285
|
-
function buildPlanDocumentationSection(context) {
|
|
7286
|
-
const hasPlan = !!context?.plan?.trim();
|
|
7287
|
-
return [
|
|
7288
|
-
``,
|
|
7289
|
-
`### Plan first, then BUILD \u2014 the plan is a step, NOT the deliverable`,
|
|
7290
|
-
`- The card is already In Progress and advances automatically (In Progress \u2192 Review PR when you open the PR). There is no plan-approval step.`,
|
|
7291
|
-
...hasPlan ? [
|
|
7292
|
-
`- A plan is already saved on the card. Keep it current with update_task_plan if your approach diverges materially \u2014 then IMPLEMENT it in code. The saved plan is not the deliverable; the working implementation is.`
|
|
7293
|
-
] : [
|
|
7294
|
-
`- No plan is saved yet: BEFORE writing any code, investigate briefly (search first, read only critical files) and save a concise implementation plan with update_task_plan (file:line citations). Saving the plan is a planning step, not the goal \u2014 immediately move on to WRITING THE CODE that implements it; never pause for approval.`
|
|
7295
|
-
],
|
|
7296
|
-
`- Your goal is to BUILD the change, not to produce a plan. After the plan is posted, actually implement it: edit source files, make the change work, then verify. Do NOT stop, go idle, or open a PR the moment the plan exists.`,
|
|
7297
|
-
`- Your pull request MUST contain the actual code implementation. Never open a plan-only or empty-diff PR \u2014 a PR that just records the plan is never the goal of auto mode unless the task explicitly asks ONLY for a plan. (If the task genuinely needs no code changes, don't open a PR at all \u2014 finish per the section below.)`,
|
|
7298
|
-
`- Identification auto-fills title, story points, and icon with quick AI guesses. After exploring, refine the title, story points, and risk with update_task_properties whenever they no longer match what the work actually is \u2014 adjust in either direction. Icons are automatic \u2014 never set them.`
|
|
7299
|
-
];
|
|
7300
|
-
}
|
|
7301
|
-
function buildPlanRevisionSection() {
|
|
7302
|
-
return [
|
|
7303
|
-
``,
|
|
7304
|
-
`### The plan can change while you sleep`,
|
|
7305
|
-
`If a turn opens with "The plan changed since this build was dispatched", honor it BEFORE your next Write or Edit:`,
|
|
7306
|
-
`1. Call get_current_plan and read the current plan in full.`,
|
|
7307
|
-
`2. Compare it against the plan you were launched with \u2014 the current plan wins.`,
|
|
7308
|
-
`3. Drop or redo whatever the revision supersedes, and say so with post_to_chat if it invalidates work you already committed.`,
|
|
7309
|
-
`Building on a superseded plan is the most expensive mistake a resumed card can make.`
|
|
7310
|
-
];
|
|
7311
|
-
}
|
|
7312
|
-
function buildNoPrWhenNoCodeSection(baseBranch) {
|
|
7313
|
-
const diffCommand = baseDiffCommand(baseBranch);
|
|
7314
|
-
return [
|
|
7315
|
-
``,
|
|
7316
|
-
`### A PR is NOT required \u2014 only open one for actual code changes`,
|
|
7317
|
-
`\`create_pull_request\` is for tasks that change code in the repo. Many tasks don't: support requests, config/credential help, answering a question, investigations, or research whose deliverable is an answer or a file rather than a diff.`,
|
|
7318
|
-
`- If you finish the work with NO code changes (an empty \`${diffCommand}\`), do NOT open a PR. An empty or throwaway PR just to "complete" the workflow is wrong \u2014 a human then has to close it.`,
|
|
7319
|
-
`- Deliver the result where it belongs: post the answer/config/findings with \`post_to_chat\`, and attach any files the user should keep with \`upload_attachment\` (any file type, up to 25MB). Never publish a deliverable as a Claude artifact or off-platform link \u2014 it belongs on the card.`,
|
|
7320
|
-
`- Then complete the card directly with \`force_update_task_status("Complete")\` \u2014 there is no PR or review step for a no-code task.`,
|
|
7321
|
-
`- When in doubt, check \`${diffCommand}\`: a real diff means open a PR; no diff means finish in chat and mark Complete.`
|
|
7322
|
-
];
|
|
7323
|
-
}
|
|
7324
|
-
function buildAttachArtifactsSection() {
|
|
7325
|
-
return [
|
|
7326
|
-
``,
|
|
7327
|
-
`### Screenshots & recordings \u2014 attach to the card AND embed in the PR description`,
|
|
7328
|
-
`If your diff changes anything a user sees rendered (e.g. \`apps/web/**\`, \`apps/sandbox/**\`, \`*.tsx\`/\`*.jsx\`, or styling), capture visual proof BEFORE you open the PR \u2014 a **screenshot** for a static change, or a short **screen recording** (\`.mp4\`/\`.webm\`/\`.gif\`, \u226425MB) for an interaction, animation, or multi-step flow. For a changed screen, capture before AND after. This is part of finishing UI work \u2014 do it unprompted, not only when a human asks. (Non-visual diffs and other documenting files \u2014 diagrams, generated reports \u2014 follow the same pattern when useful.)`,
|
|
7329
|
-
`Capture against the running preview: every pod ships headless Playwright chromium baked at \`~/.cache/ms-playwright/\`. Point it at the dev-server preview URL \u2014 \`page.screenshot({ path })\` for an image, a browser context \`recordVideo\` for a \`.webm\`.`,
|
|
7330
|
-
`Then attach it with \`upload_attachment\` (pass a workspace path): it reads the codespace-local file and hosts it on the card. It accepts images AND video (\`.mp4\`/\`.webm\`/\`.gif\`), up to 25MB.`,
|
|
7331
|
-
`- There is NO "separate pod / no filesystem access" limitation, and it is NOT images-only. Never tell the team a screenshot or recording "could not be hosted" \u2014 attach it.`,
|
|
7332
|
-
`- Pass \`tags\` when the capture is a good example of a glossary tag in some state \u2014 a screenshot of the tags page tagged \`tag\`, a board capture tagged \`task\`. Each tag's page lists its recent tagged attachments, so the label turns your screenshot into a living example of that entity and makes visual drift easy to spot. An unmatched name is reported back and never fails the upload.`,
|
|
7333
|
-
``,
|
|
7334
|
-
`**The PR description must show the visual proof, not just point at it.** \`upload_attachment\` returns a \`downloadUrl\` for every file it stores, plus a ready-to-paste markdown line. Copy that line into the \`body\` you pass to \`create_pull_request\`:`,
|
|
7335
|
-
`- Images embed inline: \`\`. Write a real caption in the alt text \u2014 "before"/"after", or what the shot demonstrates.`,
|
|
7336
|
-
`- Video and other non-image files do not render inline on GitHub. Link them instead: \`[interaction recording](<downloadUrl>)\`.`,
|
|
7337
|
-
`- Put them under a \`## Screenshots\` heading in the PR body, with before/after side by side (or one after the other) when you changed an existing screen.`,
|
|
7338
|
-
`- The URL is a signed capability our API serves; GitHub fetches and caches the image through its own proxy when the description renders, so it keeps working after the capability expires. Do NOT paste codespace-local file paths (\`/workspaces/repo/shot.png\`) \u2014 reviewers cannot open those.`,
|
|
7339
|
-
`- Keep attaching to the card as well. The card is the durable home for the capture and the only place \`tags\` apply; the PR description is what the reviewer actually reads. Do both \u2014 neither one replaces the other.`,
|
|
7340
|
-
`- If \`upload_attachment\` returns no \`downloadUrl\`, still attach to the card, and say plainly in the PR body that the embed URL was unavailable. Never silently ship a UI PR with no visual proof.`
|
|
7341
|
-
];
|
|
7342
|
-
}
|
|
7343
|
-
function buildPrGuideSection() {
|
|
7344
|
-
return [
|
|
7345
|
-
``,
|
|
7346
|
-
`### PR Guide \u2014 publish when you open the PR, refresh it on every push`,
|
|
7347
|
-
`Once your PR is open, publish a structured guide that walks a reviewer through the change with \`publish_review_guide\`. This is part of opening a PR: call it right after \`create_pull_request\` succeeds, and again whenever you push more commits to the branch.`,
|
|
7348
|
-
`- The head SHA is resolved from the local repo for you, so never hand-expand an abbreviated hash from commit output or plan text. Publish AFTER the push that created/updated the PR has landed on the branch.`,
|
|
7349
|
-
`- \`sections\` is a real top-level array ARGUMENT, not prose inside \`overview\`. Keep \`overview\` under 3000 characters \u2014 a short intro only \u2014 and put the walkthrough in \`sections\`, ordered with core behavior first and tests, migrations, generated files, and configuration later unless they are central.`,
|
|
7350
|
-
`- Explain each section's purpose and effect in plain language, and reference ONLY files this PR's diff actually changed \u2014 never context files you merely read or discussed.`,
|
|
7351
|
-
`- Anchors are optional and strict: the safest \`files\` entry is \`{"path": "..."}\` alone. If you anchor, \`hunkHeader\` must be the byte-exact full line from \`git diff <base>..HEAD -- <file> | grep '^@@'\` INCLUDING the trailing context text after the second \`@@\`, and \`startLine\`/\`endLine\` must overlap one real hunk's new-file range. When in doubt, omit the anchor fields.`,
|
|
7352
|
-
`- After any later push, call \`publish_review_guide\` again for the NEW head SHA \u2014 the card shows a "stale" banner until you republish for the current head.`,
|
|
7353
|
-
`- Best-effort: if publication fails after one corrected retry, continue. It never blocks opening or updating the PR.`
|
|
7354
|
-
];
|
|
7355
|
-
}
|
|
7356
|
-
function buildPrePrProtocolSection(context) {
|
|
7357
|
-
const base = context?.baseBranch?.trim() || "dev";
|
|
7358
|
-
return [
|
|
7359
|
-
`### Pre-PR Protocol \u2014 sync the base first, then ONE verification pass`,
|
|
7360
|
-
`CI runs the FULL suite (lint, typecheck, all test shards) on every PR \u2014 do not duplicate it locally. Local gates run ONCE, in this order:`,
|
|
7361
|
-
`1. Finish the implementation and commit it.`,
|
|
7362
|
-
`2. **Sync the base FIRST**: \`git fetch origin ${base} && git merge origin/${base} --no-edit\`. Run \`bun install\` afterwards only if the merge touched \`package.json\`/\`bun.lock\`. Merging before the gates is what makes one pass sufficient \u2014 merging after them is what forces a second run.`,
|
|
7363
|
-
`3. **One verification pass**: \`bun run check\` (lint + typecheck, fast, not lock-wrapped) then \`bun run test:affected\` (runs only the tests your diff can affect \u2014 apps/api diffs are expected to run unit tests only since CI covers the int shards; \`packages/shared\`/\`packages/db\`/root-config diffs escalate to the full suite automatically, which is still one pass, not a repeat). Selection is a heuristic \u2014 confirm it actually ran the packages you changed.`,
|
|
7364
|
-
`4. **Open the PR immediately** with \`mcp__conveyor__create_pull_request\`. Do NOT re-merge the base and do NOT re-run a gate that already passed. If \`${base}\` moved while the gates were running, open the PR anyway \u2014 CI validates against the merged base.`,
|
|
7365
|
-
`Docs/markdown/\`.claude\`-only diffs skip steps 2-3 entirely \u2014 open the PR and let CI validate.`,
|
|
7366
|
-
``,
|
|
7367
|
-
`**When a gate fails: fix, re-run only the failing tests, confirm once.**`,
|
|
7368
|
-
`- Iterate on the failures directly with the package-local script \u2014 \`bun run --cwd <pkg> test:unit <files>\` (or \`test <files>\`). It skips turbo's dep-build and the singleton lock, so it costs seconds instead of minutes. Do NOT re-run the whole gate on each iteration.`,
|
|
7369
|
-
`- Pick that file set deliberately: \`rg <ComponentName>\` across \`__tests__\` to find every suite that transitively mounts what you changed, so the targeted loop is not blind to indirect mounters.`,
|
|
7370
|
-
`- Once the targeted files pass, run the full scoped gate ONE more time as final confirmation, then open the PR. That single confirmation run \u2014 not a full gate per iteration \u2014 is what catches a suite your targeted set missed.`,
|
|
7371
|
-
`- Do NOT open a PR with a known failing gate.`,
|
|
7372
|
-
...gateFailureModes(),
|
|
7373
|
-
``,
|
|
7374
|
-
`Also before you open the PR (neither one needs a gate re-run):`,
|
|
7375
|
-
`- Reassess story points and risk against the ACTUAL diff \u2014 the estimate was made at planning time; now you know what the work really was. Correct them with \`update_task_properties\` in either direction.`,
|
|
7376
|
-
`- **UI/UX diffs:** if you changed anything rendered (\`apps/web\`, \`apps/sandbox\`, \`*.tsx\`/\`*.jsx\`, styling), capture a screenshot (static change) or short screen recording (interaction/animation), attach it to the card with \`upload_attachment\`, and **embed the returned \`downloadUrl\` in the PR description** \u2014 both, not either \u2014 see "Screenshots & recordings" below. Skip only when the diff has no visual effect.`,
|
|
7377
|
-
`- For refactors: run \`${baseDiffCommand(context?.baseBranch)}\` and confirm the public API surface (exports, function signatures) has no unintended breaking changes.`,
|
|
7378
|
-
``,
|
|
7379
|
-
`### Triaging a CI failure`,
|
|
7380
|
-
...ciTriageChecklist(context?.baseBranch)
|
|
7381
|
-
];
|
|
7382
|
-
}
|
|
7383
|
-
function buildExplorationMethodology() {
|
|
7384
|
-
return [
|
|
7385
|
-
``,
|
|
7386
|
-
`### Exploration Methodology`,
|
|
7387
|
-
`Investigate efficiently \u2014 do not read files aimlessly:`,
|
|
7388
|
-
`- Search first, read second: use grep/glob to locate relevant code, then read only the files that matter`,
|
|
7389
|
-
`- Never re-read a file already in your context \u2014 you have a large context window, scroll up instead`,
|
|
7390
|
-
`- Start with 3-5 critical files, form a hypothesis about the approach, then validate with targeted reads`,
|
|
7391
|
-
`- Stop exploring when you can cite specific \`file.ts:line\` locations and function names for every step in your plan \u2014 that's enough`
|
|
7392
|
-
];
|
|
7393
|
-
}
|
|
7394
|
-
function buildPlanCitationFormat() {
|
|
7395
|
-
return [
|
|
7396
|
-
``,
|
|
7397
|
-
`### Plan Citation Format`,
|
|
7398
|
-
`Plans must ground each change in the code. For every step that touches code:`,
|
|
7399
|
-
`- Cite the exact location as \`path/from/repo/root.ts:lineNumber\` (e.g. \`packages/conveyor-agent/src/execution/mode-prompt.ts:129\`).`,
|
|
7400
|
-
`- Name the specific function, class, constant, or JSX element being touched.`,
|
|
7401
|
-
`- When behavior hinges on a short piece of code, quote 1\u20133 lines inline instead of paraphrasing.`,
|
|
7402
|
-
`- Ranges are fine for larger edits (\`foo.ts:120-145\`). Do not cite whole files without a line.`,
|
|
7403
|
-
`- If a file doesn't exist yet, write \`NEW: path/to/new-file.ts\` and describe the surrounding module it fits into.`
|
|
7404
|
-
];
|
|
7405
|
-
}
|
|
7406
|
-
function buildDiscoveryPrompt(context, runnerMode) {
|
|
7407
|
-
const parts = [
|
|
7408
|
-
`
|
|
7409
|
-
## Mode: Discovery`,
|
|
7410
|
-
`You are in Discovery mode \u2014 helping plan and scope this task.`,
|
|
7411
|
-
`- You have read-only codebase access (can read files, run git commands, search code)`,
|
|
7412
|
-
`- Shell commands run without asking for approval \u2014 the pod is a sandbox, so investigate freely (build, test, query the dev DB, read logs). Only commands that would start the work are denied: git commit/push/merge/rebase/checkout, git apply, gh pr create, and package publish`,
|
|
7413
|
-
`- You can write plan files in .claude/plans/ only \u2014 no other file writes. Plan files on disk are NOT synced to the task; save the plan via update_task_plan`,
|
|
7414
|
-
`- Do NOT attempt to edit, write, or modify source code files \u2014 these operations will be denied`,
|
|
7415
|
-
`- If you identify code changes needed, describe them in the plan instead of implementing them`,
|
|
7416
|
-
`- You can create and manage subtasks`,
|
|
7417
|
-
`- Goal: collaborate with the user to create a clear plan`,
|
|
7418
|
-
`- Proactively fill task properties (SP, tags) as the plan takes shape`,
|
|
7419
|
-
``,
|
|
7420
|
-
`### Planning Checklist (complete ALL before calling ExitPlanMode)`,
|
|
7421
|
-
`Your PRIMARY goal is to create a thorough plan. Complete these steps in order:`,
|
|
7422
|
-
`1. Read the task description and chat history \u2014 respond to what's been discussed`,
|
|
7423
|
-
`2. Investigate the codebase using the methodology below \u2014 search first, read targeted files`,
|
|
7424
|
-
`3. Save a detailed plan via \`update_task_plan\``,
|
|
7425
|
-
`4. Set story points, risk, tags, and title via \`update_task_properties\` (icon is set automatically)`,
|
|
7426
|
-
`5. Discuss the plan with the team if they're engaged, incorporate feedback`,
|
|
7427
|
-
`6. THEN call ExitPlanMode \u2014 it is the LAST step, not the first`,
|
|
7428
|
-
...buildExplorationMethodology(),
|
|
7429
|
-
...buildPlanCitationFormat(),
|
|
7430
|
-
``,
|
|
7431
|
-
`### Self-Identification Tools`,
|
|
7432
|
-
`Use these MCP tools to set your own task properties:`,
|
|
7433
|
-
`- \`update_task_plan\` \u2014 save your plan and description`,
|
|
7434
|
-
`- \`update_task_properties\` \u2014 set title, story points, risk, and tags (any combination)`,
|
|
7435
|
-
`Note: Icons are assigned automatically during identification after planning is complete.`,
|
|
7436
|
-
``,
|
|
7437
|
-
`### Tags & Context`,
|
|
7438
|
-
`- Early in discovery, identify relevant project tags that match this task's domain`,
|
|
7439
|
-
`- Add matching tags using \`update_task_properties\` \u2014 this links relevant documentation and rules that help you plan more effectively`,
|
|
7440
|
-
`- Tags accelerate discovery by surfacing domain-specific context automatically`,
|
|
7441
|
-
``,
|
|
7442
|
-
...context?.isParentTask ? [
|
|
7443
|
-
`### Parent Task Coordination`,
|
|
7444
|
-
`You are a parent task with child tasks. Focus on breaking work into child tasks with detailed plans, not planning implementation for yourself.`,
|
|
7445
|
-
`- Use \`list_subtasks\` to review existing children. Create or update child tasks using \`create_subtask\` / \`update_subtask\`.`,
|
|
7446
|
-
`- Each child task should be a self-contained unit of work with a clear plan.`,
|
|
7447
|
-
`- Set ordering as explicit **card metadata**, not prose: pass \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` for any child that blocks on another. Leave independent children with no dependencies so the pack runner fans them out in parallel. Do NOT encode "do X after Y" only in the plan text \u2014 the runner schedules off dependsOn, not narrative.`
|
|
7448
|
-
] : [
|
|
7449
|
-
`### Self-Update vs Subtasks`,
|
|
7450
|
-
`- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
|
|
7451
|
-
`- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`
|
|
7452
|
-
],
|
|
7453
|
-
``,
|
|
7454
|
-
`### Subtask Plan Requirements`,
|
|
7455
|
-
`When creating subtasks, each MUST include a detailed \`plan\` field:`,
|
|
7456
|
-
`- Plans should be multi-step implementation guides, not vague descriptions`,
|
|
7457
|
-
`- Include concrete \`file.ts:line\` citations, function/symbol names, and short code snippets where relevant`,
|
|
7458
|
-
`- Reference existing implementations when relevant (e.g., "follow the pattern in src/services/foo.ts")`,
|
|
7459
|
-
`- Include testing requirements and acceptance criteria`,
|
|
7460
|
-
`- Set \`storyPointValue\` based on estimated complexity`,
|
|
7461
|
-
`- Express cross-child ordering as \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` \u2014 explicit dependency metadata, not "after task 2" phrasing in the plan. Independent children get no dependencies so they run in parallel.`,
|
|
7462
|
-
``,
|
|
7463
|
-
`### Plan Verification Requirements`,
|
|
7464
|
-
`Every plan MUST include a **Testing / Verification** section so the build agent has a clear definition of "done". Enumerate:`,
|
|
7465
|
-
`- The scoped verification for the change: \`bun run check\` (lint + typecheck) plus \`bun run test:affected\` \u2014 name the specific package suites the diff will touch. Docs-only plans should state that no local gates are needed (CI validates on the PR).`,
|
|
7466
|
-
`- Any task-specific end-to-end checks (manual UI walk-through, API smoke test, migration dry-run, etc.)`,
|
|
7467
|
-
`You are NOT expected to run these gates yourself \u2014 discovery is read-only. Just describe them in the plan.`,
|
|
7468
|
-
``,
|
|
7469
|
-
`### Completing Planning`,
|
|
7470
|
-
`Once ALL checklist items above are done, call the **ExitPlanMode** tool.`,
|
|
7471
|
-
`- Required before ExitPlanMode will succeed: **plan** (via update_task_plan), **story points**, **risk**, and **title** (via update_task_properties)`,
|
|
7472
|
-
`- ExitPlanMode validates these properties and marks planning as complete`,
|
|
7473
|
-
`- It does NOT start building \u2014 the team controls when to switch to Build mode`,
|
|
7474
|
-
`- Do NOT call ExitPlanMode until you have thoroughly explored the codebase and saved a detailed plan`
|
|
7475
|
-
];
|
|
7476
|
-
if (context) parts.push(...buildPropertyInstructions(context, runnerMode));
|
|
7477
|
-
return parts.join("\n");
|
|
7478
|
-
}
|
|
7479
|
-
function buildAutoPrompt(context, runnerMode) {
|
|
7480
|
-
const parts = [
|
|
7481
|
-
`
|
|
7482
|
-
## Mode: Auto`,
|
|
7483
|
-
`You are in Auto mode \u2014 operating autonomously through building \u2192 PR.`,
|
|
7484
|
-
`- You have full coding access (read, write, edit, bash, git) from the start \u2014 there is no read-only planning phase`,
|
|
7485
|
-
`- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
|
|
7486
|
-
...buildPlanDocumentationSection(context),
|
|
7487
|
-
``,
|
|
7488
|
-
`### Subtask Plan Requirements`,
|
|
7489
|
-
`When creating subtasks, each MUST include a detailed \`plan\` field:`,
|
|
7490
|
-
`- Plans should be multi-step implementation guides, not vague descriptions`,
|
|
7491
|
-
`- Include concrete \`file.ts:line\` citations, function/symbol names, and short code snippets where relevant`,
|
|
7492
|
-
`- Reference existing implementations when relevant`,
|
|
7493
|
-
`- Include testing requirements and acceptance criteria`,
|
|
7494
|
-
`- Set \`storyPointValue\` based on estimated complexity`,
|
|
7495
|
-
`- Express cross-child ordering as \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` \u2014 explicit dependency metadata, not "after task 2" phrasing in the plan. Independent children get no dependencies so they run in parallel.`,
|
|
7496
|
-
``,
|
|
7497
|
-
...buildPlanCitationFormat(),
|
|
7498
|
-
``,
|
|
7499
|
-
...context?.isParentTask ? [
|
|
7500
|
-
``,
|
|
7501
|
-
`### Parent Task Guidance`,
|
|
7502
|
-
`You are a parent task \u2014 coordinate child tasks instead of implementing directly.`,
|
|
7503
|
-
`If no children exist yet, break the work down now: save a parent-level plan with update_task_plan, then create child tasks with create_subtask (each with a detailed plan).`,
|
|
7504
|
-
`Child task status lifecycle: Open \u2192 InProgress \u2192 ReviewPR \u2192 ReviewDev \u2192 Complete.`,
|
|
7505
|
-
`Set child ordering with \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` \u2014 explicit metadata the pack runner schedules off, not order described in plan text. Independent children get none and run in parallel.`
|
|
7506
|
-
] : [
|
|
7507
|
-
...buildAttachArtifactsSection(),
|
|
7508
|
-
...buildPrGuideSection(),
|
|
7509
|
-
...buildNoPrWhenNoCodeSection(context?.baseBranch)
|
|
7510
|
-
],
|
|
7511
|
-
...buildPlanRevisionSection(),
|
|
7512
|
-
``,
|
|
7513
|
-
`### Autonomous Guidelines:`,
|
|
7514
|
-
`- Make decisions independently \u2014 do not ask the team for approval at each step`,
|
|
7515
|
-
`- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
|
|
7516
|
-
`- Investigate efficiently: search (grep/glob) to locate code, read only critical files, form a hypothesis, validate, then plan`
|
|
7517
|
-
];
|
|
7518
|
-
if (context) parts.push(...buildPropertyInstructions(context, runnerMode));
|
|
7519
|
-
return parts.join("\n");
|
|
7520
|
-
}
|
|
7521
|
-
function buildBuildingPrompt(context) {
|
|
7522
|
-
const parts = [
|
|
7523
|
-
`
|
|
7524
|
-
## Mode: Building`,
|
|
7525
|
-
`You are in Building mode \u2014 executing the plan.`,
|
|
7526
|
-
`- You have full coding access (read, write, edit, bash, git)`,
|
|
7527
|
-
`- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
|
|
7528
|
-
...context?.isParentTask ? [
|
|
7529
|
-
`- You are a parent task. Use \`list_subtasks\`, \`start_child_cloud_build\`, and subtask management tools to coordinate children.`,
|
|
7530
|
-
`- Do NOT implement code directly \u2014 fire child builds and review their work.`,
|
|
7531
|
-
`- Goal: coordinate child task execution and ensure all children complete successfully`
|
|
7532
|
-
] : [
|
|
7533
|
-
`- If this is a leaf task (no children): execute the plan directly`,
|
|
7534
|
-
`- Goal: implement the plan, run scoped verification once, open a PR when done`,
|
|
7535
|
-
``,
|
|
7536
|
-
...buildPrePrProtocolSection(context),
|
|
7537
|
-
...buildAttachArtifactsSection(),
|
|
7538
|
-
...buildPrGuideSection(),
|
|
7539
|
-
...buildNoPrWhenNoCodeSection(context?.baseBranch),
|
|
7540
|
-
...context?.isAuto || !context?.plan?.trim() ? buildPlanDocumentationSection(context) : []
|
|
7541
|
-
],
|
|
7542
|
-
...buildPlanRevisionSection()
|
|
7543
|
-
];
|
|
7544
|
-
if (context) parts.push(...buildPropertyInstructions(context));
|
|
7545
|
-
return parts.join("\n");
|
|
7546
|
-
}
|
|
7547
|
-
function buildModePrompt(agentMode, context, runnerMode) {
|
|
7548
|
-
switch (agentMode) {
|
|
7549
|
-
case "discovery":
|
|
7550
|
-
return buildDiscoveryPrompt(context, runnerMode);
|
|
7551
|
-
case "building":
|
|
7552
|
-
return buildBuildingPrompt(context);
|
|
7553
|
-
case "review":
|
|
7554
|
-
return buildReviewPrompt(context);
|
|
7555
|
-
case "auto":
|
|
7556
|
-
return buildAutoPrompt(context, runnerMode);
|
|
7557
|
-
case "chat":
|
|
7558
|
-
return buildChatPrompt(context);
|
|
7559
|
-
default:
|
|
7560
|
-
return null;
|
|
7561
|
-
}
|
|
7562
|
-
}
|
|
7563
|
-
function buildChatPrompt(context) {
|
|
7564
|
-
const base = context?.baseBranch?.trim() || "dev";
|
|
7565
|
-
return [
|
|
7566
|
-
`
|
|
7567
|
-
## Mode: Chat`,
|
|
7568
|
-
`You are in Chat mode \u2014 a conversational assistant working directly with the user on this card.`,
|
|
7569
|
-
`- Respond conversationally to the user in chat. Ask clarifying questions when useful; this is a back-and-forth, not an autonomous build.`,
|
|
7570
|
-
`- You have full read/write access to the workspace and can run non-destructive shell commands, so you CAN create files (notes, scripts, docs, data, diagrams, etc.) when they help the user.`,
|
|
7571
|
-
`- This card starts Unidentified. That is correct \u2014 it stays a conversation until the work becomes a development task (see below).`,
|
|
7572
|
-
``,
|
|
7573
|
-
`### Deliverables \u2014 attach files to the card`,
|
|
7574
|
-
`- When you create a file the user should keep, attach it to the card with the \`upload_attachment\` tool so it shows up on the card. Mention in chat what you attached.`,
|
|
7575
|
-
`- \`upload_attachment\` accepts any file type (markdown, PDF, HTML, text, data, images) up to 25MB \u2014 this is how docs are delivered. Do NOT publish deliverables as a Claude artifact or any other off-platform link; the card is the system of record.`,
|
|
7576
|
-
`- For conversational work, attachments are the deliverable. Do NOT open a PR to hand over a document or an answer.`,
|
|
7577
|
-
``,
|
|
7578
|
-
`### Turning into a development task`,
|
|
7579
|
-
`- If the conversation becomes a request to change the code, escalate the card first: save a plan with \`update_task_plan\`. That first plan identifies the card and moves it to In Progress.`,
|
|
7580
|
-
`- The plan is also the gate on your PR path: \`create_pull_request\`, \`git push\` and \`gh pr\` are DENIED until a plan is saved, and allowed afterwards. Do not try to work around a denial \u2014 save the plan.`,
|
|
7581
|
-
`- Once the plan is saved, work like a normal build: implement it, then run ONE verification pass (\`bun run check\` plus \`bun run test:affected\`) after syncing the base (\`git fetch origin ${base} && git merge origin/${base} --no-edit\`), then open the PR with \`mcp__conveyor__create_pull_request\`.`,
|
|
7582
|
-
`- Keep talking to the user while you build \u2014 this is still their card.`,
|
|
7583
|
-
``,
|
|
7584
|
-
`### Finishing the conversation`,
|
|
7585
|
-
`- When the user indicates they are done (or explicitly asks to wrap up / close the card), call \`force_update_task_status\` with status \`"Complete"\`. It works from either status, so a card still sitting Unidentified can be completed the same way as one that reached In Progress.`,
|
|
7586
|
-
`- If you opened a PR, do NOT complete the card \u2014 the PR review flow takes it from there.`,
|
|
7587
|
-
`- If the user is still engaged, keep helping \u2014 only complete the card once the interaction has concluded.`,
|
|
7588
|
-
`- Do not complete the card while you still owe the user a response or an attachment.`
|
|
7589
|
-
].join("\n");
|
|
7590
|
-
}
|
|
7591
|
-
function buildReviewTagSection(context) {
|
|
7592
|
-
const assignedIds = new Set(context?.taskTagIds ?? []);
|
|
7593
|
-
const assigned = (context?.projectTags ?? []).filter((t) => assignedIds.has(t.id));
|
|
7594
|
-
if (assigned.length === 0) return [];
|
|
7595
|
-
const parts = [`### Card Tags & Domain Context`, `This card carries these project glossary tags:`];
|
|
7596
|
-
for (const tag of assigned) parts.push(...formatTagWithContextPaths(tag));
|
|
7597
|
-
parts.push(
|
|
7598
|
-
``,
|
|
7599
|
-
`A tag's rules and overview define the conventions the code under review must follow. Use them when you judge Pattern Consistency:`,
|
|
7600
|
-
`- Read the tag entries that match the files in this diff. The "Reference Guides" section of the task brief lists the same tags with a one-line summary of each linked doc.`,
|
|
7601
|
-
`- Call get_tag("<name>") for a term's full spec (overview, linked files, hierarchy) when the linked docs are not enough.`,
|
|
7602
|
-
`- A change that contradicts a tagged system's documented conventions is a review finding, even when it reads fine on its own.`,
|
|
7603
|
-
`- Skip the tags this diff does not touch \u2014 do not read them all up front.`,
|
|
7604
|
-
`- If the diff changes how a tagged system behaves and the tag's overview is now wrong, say so in your review.`,
|
|
7605
|
-
``
|
|
7606
|
-
);
|
|
7607
|
-
return parts;
|
|
7608
|
-
}
|
|
7609
|
-
function buildReviewPrompt(context) {
|
|
7610
|
-
const parts = [
|
|
7611
|
-
`
|
|
7612
|
-
## Mode: Review`,
|
|
7613
|
-
`You are in Review mode \u2014 performing code review with fix capability.`,
|
|
7614
|
-
`- You have full write access \u2014 you can audit code, make fixes, push changes, and run tests`,
|
|
7615
|
-
`- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
|
|
7616
|
-
``
|
|
7617
|
-
];
|
|
7618
|
-
if (context?.isParentTask) {
|
|
7619
|
-
parts.push(
|
|
7620
|
-
`### Parent Task Review`,
|
|
7621
|
-
`You are reviewing and coordinating child tasks.`,
|
|
7622
|
-
`- Use \`list_subtasks\` to see current child task state and progress.`,
|
|
7623
|
-
`- For children in ReviewPR status: review their code quality and merge with \`approve_and_merge_pr\`.`,
|
|
7624
|
-
`- For children with failing CI: check with \`get_execution_logs(childTaskId)\` and escalate if stuck.`,
|
|
7625
|
-
`- Fire next child builds with \`start_child_cloud_build\` when ready.`,
|
|
7626
|
-
`- Create follow-up tasks for issues discovered during review.`,
|
|
7627
|
-
``,
|
|
7628
|
-
`### Coordination Workflow`,
|
|
7629
|
-
`1. Check child task statuses with \`list_subtasks\``,
|
|
7630
|
-
`2. Review completed children \u2014 check PRs, run tests if needed`,
|
|
7631
|
-
`3. Approve and merge passing PRs`,
|
|
7632
|
-
`4. Fire builds for children that are ready`,
|
|
7633
|
-
`5. Create follow-up tasks for anything out of scope`,
|
|
7634
|
-
`6. As children complete, correct their story points with update_subtask (storyPointValue) when the actual work diverged from the estimate \u2014 either direction`
|
|
7635
|
-
);
|
|
7636
|
-
} else {
|
|
7637
|
-
const tagSection = buildReviewTagSection(context);
|
|
7638
|
-
const patternConsistency = tagSection.length ? `- **Pattern Consistency**: Does the code follow existing patterns in the codebase? Check nearby files AND the card's tag rules/overviews (see "Card Tags & Domain Context" below).` : `- **Pattern Consistency**: Does the code follow existing patterns in the codebase? Check nearby files.`;
|
|
7639
|
-
parts.push(
|
|
7640
|
-
`### Code Review Process`,
|
|
7641
|
-
`1. Run \`${baseDiffCommand(context?.baseBranch)}\` to see all changes in this PR`,
|
|
7642
|
-
`2. Read the task plan to understand the intended changes`,
|
|
7643
|
-
`3. Explore the surrounding codebase to verify pattern consistency`,
|
|
7644
|
-
`4. Review against the criteria below`,
|
|
7645
|
-
``,
|
|
7646
|
-
`### Review Criteria`,
|
|
7647
|
-
`- **Correctness**: Does the code do what the plan says? Logic errors, off-by-one, race conditions?`,
|
|
7648
|
-
patternConsistency,
|
|
7649
|
-
`- **Security**: No hardcoded secrets, no injection vulnerabilities, proper input validation at boundaries.`,
|
|
7650
|
-
`- **Performance**: No unnecessary loops, no N+1 queries, no blocking in async contexts.`,
|
|
7651
|
-
`- **Error Handling**: Appropriate error handling at system boundaries. No swallowed errors.`,
|
|
7652
|
-
`- **Test Coverage**: Are new code paths tested? Edge cases covered?`,
|
|
7653
|
-
`- **TypeScript Best Practices**: Proper typing (no unnecessary \`any\`), correct React patterns, proper async/await.`,
|
|
7654
|
-
`- **Naming & Readability**: Clear names, no misleading comments, self-documenting code.`,
|
|
7655
|
-
``,
|
|
7656
|
-
`### Fix Capability`,
|
|
7657
|
-
`You have full write access. If you find issues:`,
|
|
7658
|
-
`- **Small fixes**: Make the fix directly, commit, and push. Then re-review.`,
|
|
7659
|
-
`- **Larger issues**: Use \`request_code_changes\` to flag them for the team.`,
|
|
7660
|
-
`- After pushing fixes, wait for CI to pass before approving.`,
|
|
7661
|
-
``,
|
|
7662
|
-
`### Output \u2014 You MUST do exactly ONE of:`,
|
|
7663
|
-
``,
|
|
7664
|
-
`#### If code passes review (or after you've fixed all issues):`,
|
|
7665
|
-
`Use the \`approve_code_review\` tool with a brief summary of what looks good.`,
|
|
7666
|
-
``,
|
|
7667
|
-
`#### If changes are needed that you cannot fix:`,
|
|
7668
|
-
`Use the \`request_code_changes\` tool with specific issues:`,
|
|
7669
|
-
`- Reference specific files and line numbers`,
|
|
7670
|
-
`- Explain what's wrong and suggest fixes`,
|
|
7671
|
-
`- Focus on substantive issues, not style nitpicks (linting handles that)`,
|
|
7672
|
-
``,
|
|
7673
|
-
`#### Risk level (required on BOTH tools):`,
|
|
7674
|
-
`Every verdict MUST include a \`risk\` level \u2014 judge it by the surface area the change touches:`,
|
|
7675
|
-
`- \`critical\`: touches critical/foundational surface (auth, billing, data integrity, migrations)`,
|
|
7676
|
-
`- \`high\`: touches important surface with broad blast radius`,
|
|
7677
|
-
`- \`medium\`: moderate, contained surface area`,
|
|
7678
|
-
`- \`low\`: small or isolated change`,
|
|
7679
|
-
`The task may already have a risk level set. If your review makes you disagree with it, set the level you believe is correct \u2014 you have the authority to override it in either direction.`,
|
|
7680
|
-
``,
|
|
7681
|
-
`#### Story points (correct if wrong):`,
|
|
7682
|
-
`You can see the full final diff \u2014 if the story-point estimate no longer matches the actual size of the work, correct it with update_task_properties (storyPointValue), in either direction. The card should record what the work actually was, not what it looked like at planning time.`,
|
|
7683
|
-
``,
|
|
7684
|
-
...tagSection,
|
|
7685
|
-
`### Previous Review Feedback`,
|
|
7686
|
-
`If previous review feedback is present in the chat history, verify those specific issues were addressed before raising new concerns.`,
|
|
7687
|
-
``,
|
|
7688
|
-
`### Rules`,
|
|
7689
|
-
`- Do NOT re-review things CI already validates (formatting, lint rules).`,
|
|
7690
|
-
`- Be concise \u2014 actionable specifics over general observations.`,
|
|
7691
|
-
`- Max 5-7 issues per review. Prioritize the most important ones.`
|
|
7692
|
-
);
|
|
7693
|
-
}
|
|
7694
|
-
return parts.join("\n");
|
|
7695
|
-
}
|
|
7696
|
-
|
|
7697
|
-
// src/execution/system-prompt.ts
|
|
7698
7797
|
function repoHasScript(workspaceDir, script) {
|
|
7699
7798
|
try {
|
|
7700
7799
|
const pkg = JSON.parse(readFileSync(join11(workspaceDir, "package.json"), "utf8"));
|
|
@@ -7722,7 +7821,7 @@ Environment (ready, no setup required):`,
|
|
|
7722
7821
|
`
|
|
7723
7822
|
Workflow:`,
|
|
7724
7823
|
`- You can draft and iterate on plans in .claude/plans/*.md, but files on disk are NOT synced to the task.`,
|
|
7725
|
-
`- Save the plan to the task with
|
|
7824
|
+
`- Save the plan to the task with update_task \u2014 this is the only way the plan is persisted.`,
|
|
7726
7825
|
`- After saving the plan, call post_to_chat with a short summary for the team (your turn output is NOT posted to chat \u2014 they only see what you post), then end your turn. Do NOT attempt to execute the plan yourself.`,
|
|
7727
7826
|
`- A separate task agent will handle execution after the team reviews and approves your plan.`
|
|
7728
7827
|
];
|
|
@@ -7749,7 +7848,7 @@ function buildActivePreamble(context, workspaceDir) {
|
|
|
7749
7848
|
`You are an AI project manager in ACTIVE mode for the "${context.title}" project.`,
|
|
7750
7849
|
`You have direct coding access to the repository at ${workspaceDir}.`,
|
|
7751
7850
|
`You can edit files, run tests, and make commits.`,
|
|
7752
|
-
`You still have access to all PM tools (subtasks,
|
|
7851
|
+
`You still have access to all PM tools (subtasks, update_task, chat).`,
|
|
7753
7852
|
`
|
|
7754
7853
|
Environment (ready, no setup required):`,
|
|
7755
7854
|
`- Repository is cloned at your current working directory.`,
|
|
@@ -7804,12 +7903,15 @@ Git:`,
|
|
|
7804
7903
|
`- If \`git push\` is rejected as non-fast-forward, run \`git push --force-with-lease origin ${context.githubBranch}\`. This branch is exclusively yours, so force-with-lease is safe.`
|
|
7805
7904
|
];
|
|
7806
7905
|
}
|
|
7906
|
+
function buildPackPrompt(mode, context, config, setupLog) {
|
|
7907
|
+
return mode === "pack" && config.packExecution !== "fan-out" ? buildSinglePodPackPrompt(context, config, setupLog) : buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
7908
|
+
}
|
|
7807
7909
|
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
7808
7910
|
const isPm = mode === "pm";
|
|
7809
7911
|
const isPmActive = isPm && agentMode === "building";
|
|
7810
7912
|
const isPackRunner = mode === "pack" || isPm && !!config.isAuto && !!context.isParentTask;
|
|
7811
7913
|
if (isPackRunner) {
|
|
7812
|
-
return
|
|
7914
|
+
return buildPackPrompt(mode, context, config, setupLog);
|
|
7813
7915
|
}
|
|
7814
7916
|
const parts = isPmActive ? buildActivePreamble(context, config.workspaceDir) : isPm ? buildPmPreamble(context) : buildTaskAgentPreamble(context, config.workspaceDir);
|
|
7815
7917
|
if (setupLog.length > 0) {
|
|
@@ -7869,6 +7971,19 @@ function detectRelaunchScenario(context, trustChatHistory = false) {
|
|
|
7869
7971
|
const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
|
|
7870
7972
|
return hasNewUserMessages ? "feedback_relaunch" : "idle_relaunch";
|
|
7871
7973
|
}
|
|
7974
|
+
function branchRule(context, forReview = false) {
|
|
7975
|
+
return forReview ? `Work on the git branch "${context.githubBranch}". Stay on this branch for the entire review. Do not checkout or create other branches.` : `Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`;
|
|
7976
|
+
}
|
|
7977
|
+
function newMessagesBlock(messages) {
|
|
7978
|
+
return [
|
|
7979
|
+
`
|
|
7980
|
+
New messages since your last run:`,
|
|
7981
|
+
...messages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`)
|
|
7982
|
+
];
|
|
7983
|
+
}
|
|
7984
|
+
function pullRequestLine(context) {
|
|
7985
|
+
return context.githubPRUrl ? `An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch to update it. Do NOT create a new PR.` : `When finished, use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`;
|
|
7986
|
+
}
|
|
7872
7987
|
function buildRelaunchWithSession(mode, context, agentMode, isAuto) {
|
|
7873
7988
|
const scenario = detectRelaunchScenario(context);
|
|
7874
7989
|
const hasPriorTurn = !!context.lastSeenMessageId || !!context.claudeSessionId;
|
|
@@ -7891,27 +8006,17 @@ function buildRelaunchWithSession(mode, context, agentMode, isAuto) {
|
|
|
7891
8006
|
const newMessages = allNew.filter((m) => m.role === "user");
|
|
7892
8007
|
parts.push(
|
|
7893
8008
|
`You have been relaunched with new feedback.`,
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
New messages since your last run:`,
|
|
7897
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
8009
|
+
branchRule(context),
|
|
8010
|
+
...newMessagesBlock(newMessages),
|
|
7898
8011
|
`
|
|
7899
8012
|
Address the requested changes. Do NOT re-investigate the codebase from scratch or write a new plan \u2014 review the feedback and implement the changes directly.`,
|
|
7900
|
-
`Commit and push your updates
|
|
8013
|
+
`Commit and push your updates.`,
|
|
8014
|
+
pullRequestLine(context)
|
|
7901
8015
|
);
|
|
7902
|
-
if (context.githubPRUrl) {
|
|
7903
|
-
parts.push(
|
|
7904
|
-
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch. Do NOT create a new PR.`
|
|
7905
|
-
);
|
|
7906
|
-
} else {
|
|
7907
|
-
parts.push(
|
|
7908
|
-
`When finished, use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI.`
|
|
7909
|
-
);
|
|
7910
|
-
}
|
|
7911
8016
|
} else {
|
|
7912
8017
|
parts.push(
|
|
7913
8018
|
`You were relaunched but no new instructions have been given since your last run.`,
|
|
7914
|
-
|
|
8019
|
+
branchRule(context),
|
|
7915
8020
|
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
7916
8021
|
`Review the current state of the codebase and verify everything is working correctly.`
|
|
7917
8022
|
);
|
|
@@ -8022,9 +8127,9 @@ function buildFreshInstructions(isPm, isAutoMode, context, agentMode) {
|
|
|
8022
8127
|
if (isPm && agentMode === "building") {
|
|
8023
8128
|
return [
|
|
8024
8129
|
`Your plan has been approved. Begin implementing it now.`,
|
|
8025
|
-
|
|
8130
|
+
branchRule(context),
|
|
8026
8131
|
`Start by reading the relevant source files mentioned in the plan, then write code.`,
|
|
8027
|
-
|
|
8132
|
+
pullRequestLine(context),
|
|
8028
8133
|
`
|
|
8029
8134
|
CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or go idle without making code changes.`,
|
|
8030
8135
|
`Your FIRST action must be reading source files from the plan, then immediately writing code.`,
|
|
@@ -8044,7 +8149,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
|
|
|
8044
8149
|
return [
|
|
8045
8150
|
`You are operating autonomously. No plan is saved on this card yet.`,
|
|
8046
8151
|
`1. Search the codebase (grep/glob) to locate relevant files, then read the critical ones`,
|
|
8047
|
-
`2. Save a concise plan with
|
|
8152
|
+
`2. Save a concise plan with update_task BEFORE writing any code \u2014 the plan is a record for the team, not a gate; never pause or wait for approval`,
|
|
8048
8153
|
`3. Implement the work, keeping the plan current if your approach changes materially`,
|
|
8049
8154
|
`4. Refine story points, tags, and title (update_task_properties) if they look like placeholders`,
|
|
8050
8155
|
`Do NOT wait for team input \u2014 proceed autonomously.`
|
|
@@ -8056,7 +8161,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
|
|
|
8056
8161
|
`Review existing subtasks via \`list_subtasks\` and the chat history before taking action.`,
|
|
8057
8162
|
`Read the task description and chat history carefully \u2014 the team has provided the initial context below. Acknowledge what they've asked for and respond in chat before taking silent tool actions.`,
|
|
8058
8163
|
`Start planning now \u2014 explore the codebase, ask clarifying questions if needed, and propose a subtask breakdown. Do not wait for additional team input before engaging.`,
|
|
8059
|
-
`When you finish planning, save the plan with
|
|
8164
|
+
`When you finish planning, save the plan with update_task, post a short summary for the team with post_to_chat (your turn output is NOT shown in chat), then end your turn.`
|
|
8060
8165
|
];
|
|
8061
8166
|
}
|
|
8062
8167
|
if (isPm) {
|
|
@@ -8065,20 +8170,20 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
|
|
|
8065
8170
|
`Your job right now is to help the team turn this request into a clear, well-scoped plan. Be a helpful planning partner: think out loud, surface trade-offs, and keep the human in the loop.`,
|
|
8066
8171
|
`Read the task description and chat history carefully \u2014 the team has provided the initial context below. Acknowledge what they've asked for and respond in chat before taking silent tool actions.`,
|
|
8067
8172
|
`Start planning now \u2014 explore the codebase, ask clarifying questions if anything is ambiguous, and draft a plan. Do not wait for additional team input before engaging; the initial message IS the team engaging.`,
|
|
8068
|
-
`When you finish planning, save the plan with
|
|
8173
|
+
`When you finish planning, save the plan with update_task, post a summary of the plan for the team with post_to_chat (your turn output is NOT shown in chat), then end your turn. A separate task agent will execute the plan after review.`
|
|
8069
8174
|
];
|
|
8070
8175
|
}
|
|
8071
8176
|
return buildFreshLeafInstructions(context, isAutoMode);
|
|
8072
8177
|
}
|
|
8073
8178
|
function buildFreshLeafInstructions(context, isAutoMode) {
|
|
8074
8179
|
const parts = context.plan?.trim() ? [`Start now by reading the source files the plan names, then writing code.`] : [
|
|
8075
|
-
`No plan is saved on this card yet. Investigate the task briefly (search first, read only critical files), then save a concise implementation plan with
|
|
8180
|
+
`No plan is saved on this card yet. Investigate the task briefly (search first, read only critical files), then save a concise implementation plan with update_task (file:line citations) BEFORE you start writing code.`,
|
|
8076
8181
|
`The plan is a step, not the goal: the moment it's posted, IMPLEMENT it in code \u2014 do NOT pause, wait for approval, or treat the posted plan as the deliverable. The card is already In Progress and advances automatically.`
|
|
8077
8182
|
];
|
|
8078
8183
|
const base = context.baseBranch?.trim() || "dev";
|
|
8079
8184
|
parts.push(
|
|
8080
8185
|
`Post to chat when you begin implementing and again when the PR is ready.`,
|
|
8081
|
-
`
|
|
8186
|
+
`Pre-PR order (\`/conveyor-build\` has the detail): commit, then sync the base (\`git fetch origin ${base} && git merge origin/${base} --no-edit\`), then ONE verification pass \u2014 \`bun run check\` (lint + typecheck) plus \`bun run test:affected\` (tests scoped to your diff; docs-only changes need no local gates) \u2014 then open the PR. Do NOT re-merge the base or re-run a gate that already passed.`,
|
|
8082
8187
|
`If a gate fails, iterate on just the failing test files (\`bun run --cwd <pkg> test:unit <files>\`) and confirm with a single final scoped-gate run. Do NOT open a PR with known failing gates.`,
|
|
8083
8188
|
`Open the PR with mcp__conveyor__create_pull_request when the work is done and the gates are green.`
|
|
8084
8189
|
);
|
|
@@ -8095,19 +8200,7 @@ CRITICAL: You are in Auto mode. Your job is to BUILD the change, not to produce
|
|
|
8095
8200
|
return parts;
|
|
8096
8201
|
}
|
|
8097
8202
|
function buildFreshCodeReviewInstructions(context) {
|
|
8098
|
-
const parts = [
|
|
8099
|
-
`Perform the automated code review for this PR.`,
|
|
8100
|
-
`Work on the git branch "${context.githubBranch}". Stay on this branch for the entire review. Do not checkout or create other branches.`,
|
|
8101
|
-
`Start by establishing what you are actually reviewing${context.githubPRUrl ? `: \`gh pr view ${context.githubPRUrl} --json state,baseRefName,headRefOid,files\`` : ` \u2014 the PR's own base ref, head SHA and changed-file list`}. Do this BEFORE any git diff: the PR's base is often not the project default branch, and asking the PR costs one call instead of six.`,
|
|
8102
|
-
`Then inspect the submitted changes with \`${baseDiffCommand(context.baseBranch)}\`. If that disagrees with the PR's file list, trust the PR: diff the head SHA directly (\`git diff <headSha>^ <headSha>\`) and review that.`,
|
|
8103
|
-
`Your checkout can be stale, and so can the remote branch tip \u2014 a merged PR's commit may be reachable only by SHA. If the PR reports state MERGED or CLOSED, say so and review the recorded head SHA rather than assuming your working tree matches it.`,
|
|
8104
|
-
`Read the code under review out of the PR's commit, NOT the working tree: \`git show <sha>:<path>\` and \`git grep <pattern> <sha>\`. Grepping the working tree when the PR is based elsewhere returns confident, wrong answers \u2014 it has produced false review findings.`,
|
|
8105
|
-
`Review for correctness, security, performance, error handling, test coverage, and consistency with existing patterns.`,
|
|
8106
|
-
`Consult the card's tag Reference Guides in the brief above for the domain rules that apply to this diff \u2014 they are the conventions the change must follow, and a contradiction of them is a review finding.`,
|
|
8107
|
-
`If small fixes are needed, make them directly, commit, push, and re-review the result.`,
|
|
8108
|
-
`Use approve_code_review when the PR passes review.`,
|
|
8109
|
-
`Use request_code_changes when substantive issues remain that you cannot fix directly.`
|
|
8110
|
-
];
|
|
8203
|
+
const parts = [...buildSkillInvocation("/conveyor-review"), branchRule(context, true)];
|
|
8111
8204
|
if (context.githubPRUrl) {
|
|
8112
8205
|
parts.push(`The PR under review is ${context.githubPRUrl}. Do not create a new PR.`);
|
|
8113
8206
|
}
|
|
@@ -8120,22 +8213,20 @@ function buildFeedbackInstructions(context, isPm, agentMode, isAuto) {
|
|
|
8120
8213
|
const parts2 = [
|
|
8121
8214
|
`You were relaunched with new feedback since your last run.`,
|
|
8122
8215
|
`You are the project manager for this task.`,
|
|
8123
|
-
|
|
8124
|
-
New messages since your last run:`,
|
|
8125
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`)
|
|
8216
|
+
...newMessagesBlock(newMessages)
|
|
8126
8217
|
];
|
|
8127
8218
|
if (isAuto && (agentMode === "building" || agentMode === "review")) {
|
|
8128
8219
|
parts2.push(
|
|
8129
8220
|
`
|
|
8130
8221
|
Your plan has been approved. Address the feedback above, then begin implementing.`,
|
|
8131
|
-
|
|
8222
|
+
branchRule(context),
|
|
8132
8223
|
`Start by reading the relevant source files mentioned in the plan, then write code.`,
|
|
8133
|
-
|
|
8224
|
+
pullRequestLine(context)
|
|
8134
8225
|
);
|
|
8135
8226
|
} else if (isAuto) {
|
|
8136
8227
|
parts2.push(
|
|
8137
8228
|
`
|
|
8138
|
-
You are in auto mode. Address the feedback above and continue building \u2014 update the saved plan with
|
|
8229
|
+
You are in auto mode. Address the feedback above and continue building \u2014 update the saved plan with update_task if the feedback changes your approach.`,
|
|
8139
8230
|
`Do NOT wait for additional team input \u2014 the messages above ARE the team's input. Proceed autonomously.`
|
|
8140
8231
|
);
|
|
8141
8232
|
} else {
|
|
@@ -8148,28 +8239,18 @@ Review these messages and wait for the team to provide instructions before takin
|
|
|
8148
8239
|
}
|
|
8149
8240
|
const parts = [
|
|
8150
8241
|
`You have been relaunched to address feedback on your previous work.`,
|
|
8151
|
-
|
|
8242
|
+
branchRule(context),
|
|
8152
8243
|
`Start by running \`${baseDiffCommand(context.baseBranch, "--stat")}\` to review what you already committed on this branch.`,
|
|
8153
|
-
|
|
8154
|
-
New messages since your last run:`,
|
|
8155
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
8244
|
+
...newMessagesBlock(newMessages),
|
|
8156
8245
|
`
|
|
8157
8246
|
Address the requested changes directly. Do NOT re-investigate the codebase from scratch or write a new plan \u2014 go straight to implementing the feedback.`,
|
|
8158
8247
|
// Scoped to what the follow-up actually changed. An unconditional full
|
|
8159
8248
|
// re-verify on every relaunch made a one-line or docs-only follow-up cost
|
|
8160
8249
|
// the same gate time as the original build.
|
|
8161
8250
|
`Re-verify in proportion to what THIS follow-up changed, not the whole branch: a docs/\`.claude\`-only change needs no local gates; a code change runs \`bun run check\` plus the directly-affected test files (\`bun run --cwd <pkg> test:unit <files>\`); escalate to a full \`bun run test:affected\` only when the follow-up touched \`packages/shared\`, \`packages/db\`, or root config, or when no gate has ever run on this branch. Fix any failures before pushing \u2014 relaunches are the most common place verification gets skipped.`,
|
|
8162
|
-
`Implement your updates and open a PR when finished
|
|
8251
|
+
`Implement your updates and open a PR when finished.`,
|
|
8252
|
+
pullRequestLine(context)
|
|
8163
8253
|
];
|
|
8164
|
-
if (context.githubPRUrl) {
|
|
8165
|
-
parts.push(
|
|
8166
|
-
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch to update it. Do NOT create a new PR.`
|
|
8167
|
-
);
|
|
8168
|
-
} else {
|
|
8169
|
-
parts.push(
|
|
8170
|
-
`When finished, use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
8171
|
-
);
|
|
8172
|
-
}
|
|
8173
8254
|
return parts;
|
|
8174
8255
|
}
|
|
8175
8256
|
function buildIdleRelaunchInstructions(context, isPm, agentMode, isAuto) {
|
|
@@ -8183,7 +8264,7 @@ function buildIdleRelaunchInstructions(context, isPm, agentMode, isAuto) {
|
|
|
8183
8264
|
}
|
|
8184
8265
|
return [
|
|
8185
8266
|
`You were relaunched in auto mode. Continue building autonomously.`,
|
|
8186
|
-
`No plan is saved on this card yet \u2014 save a concise plan with
|
|
8267
|
+
`No plan is saved on this card yet \u2014 save a concise plan with update_task before writing further code; never pause or wait for approval.`,
|
|
8187
8268
|
`Do NOT wait for instructions or go idle \u2014 you are in auto mode.`
|
|
8188
8269
|
];
|
|
8189
8270
|
}
|
|
@@ -8197,7 +8278,7 @@ function buildIdleRelaunchInstructions(context, isPm, agentMode, isAuto) {
|
|
|
8197
8278
|
const isAutoMode = agentMode === "auto" || agentMode === "building" || isAuto;
|
|
8198
8279
|
const parts = [
|
|
8199
8280
|
`You were relaunched but no new instructions have been given since your last run.`,
|
|
8200
|
-
|
|
8281
|
+
branchRule(context),
|
|
8201
8282
|
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`
|
|
8202
8283
|
];
|
|
8203
8284
|
if (isAutoMode) {
|
|
@@ -8252,7 +8333,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
8252
8333
|
parts.push(...buildFeedbackInstructions(context, isPm, agentMode, isAuto));
|
|
8253
8334
|
return parts;
|
|
8254
8335
|
}
|
|
8255
|
-
async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
8336
|
+
async function buildInitialPrompt(mode, context, isAuto, agentMode, packExecution) {
|
|
8256
8337
|
const isPackRunner = mode === "pack" || mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
8257
8338
|
if (!isPackRunner) {
|
|
8258
8339
|
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode, isAuto);
|
|
@@ -8266,12 +8347,13 @@ async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
|
8266
8347
|
scenario = "fresh";
|
|
8267
8348
|
}
|
|
8268
8349
|
const body = await buildTaskBody(context, mode);
|
|
8269
|
-
const
|
|
8350
|
+
const singlePodPack = mode === "pack" && packExecution !== "fan-out";
|
|
8351
|
+
const instructions = isPackRunner ? singlePodPack ? buildSinglePodPackInstructions(context, scenario) : buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode, isAuto);
|
|
8270
8352
|
return [...body, ...instructions].join("\n");
|
|
8271
8353
|
}
|
|
8272
8354
|
|
|
8273
8355
|
// src/tools/task-context-tools.ts
|
|
8274
|
-
import { z as
|
|
8356
|
+
import { z as z13 } from "zod";
|
|
8275
8357
|
|
|
8276
8358
|
// ../shared/dist/tool-contracts/index.js
|
|
8277
8359
|
var f = {
|
|
@@ -8300,14 +8382,14 @@ var f = {
|
|
|
8300
8382
|
return { kind: "nullable", inner };
|
|
8301
8383
|
}
|
|
8302
8384
|
};
|
|
8303
|
-
function compileString(
|
|
8304
|
-
let schema =
|
|
8385
|
+
function compileString(z20, spec) {
|
|
8386
|
+
let schema = z20.string();
|
|
8305
8387
|
if (spec.min !== void 0) schema = schema.min(spec.min);
|
|
8306
8388
|
if (spec.max !== void 0) schema = schema.max(spec.max);
|
|
8307
8389
|
return schema;
|
|
8308
8390
|
}
|
|
8309
|
-
function compileNumber(
|
|
8310
|
-
let schema =
|
|
8391
|
+
function compileNumber(z20, spec) {
|
|
8392
|
+
let schema = z20.number();
|
|
8311
8393
|
if (spec.int) schema = schema.int();
|
|
8312
8394
|
if (spec.positive) schema = schema.positive();
|
|
8313
8395
|
if (spec.nonnegative) schema = schema.nonnegative();
|
|
@@ -8315,41 +8397,49 @@ function compileNumber(z19, spec) {
|
|
|
8315
8397
|
if (spec.max !== void 0) schema = schema.max(spec.max);
|
|
8316
8398
|
return schema;
|
|
8317
8399
|
}
|
|
8318
|
-
function compileArray(
|
|
8319
|
-
let schema =
|
|
8400
|
+
function compileArray(z20, spec) {
|
|
8401
|
+
let schema = z20.array(compileField(z20, spec.item));
|
|
8320
8402
|
if (spec.min !== void 0) schema = schema.min(spec.min);
|
|
8321
8403
|
return schema;
|
|
8322
8404
|
}
|
|
8323
|
-
function compileBase(
|
|
8405
|
+
function compileBase(z20, spec) {
|
|
8324
8406
|
switch (spec.kind) {
|
|
8325
8407
|
case "string":
|
|
8326
|
-
return compileString(
|
|
8408
|
+
return compileString(z20, spec);
|
|
8327
8409
|
case "number":
|
|
8328
|
-
return compileNumber(
|
|
8410
|
+
return compileNumber(z20, spec);
|
|
8329
8411
|
case "boolean":
|
|
8330
|
-
return
|
|
8412
|
+
return z20.boolean();
|
|
8331
8413
|
case "enum":
|
|
8332
|
-
return
|
|
8414
|
+
return z20.enum([...spec.values]);
|
|
8333
8415
|
case "array":
|
|
8334
|
-
return compileArray(
|
|
8416
|
+
return compileArray(z20, spec);
|
|
8335
8417
|
case "object":
|
|
8336
|
-
return
|
|
8418
|
+
return z20.object(compileShape(z20, spec.fields));
|
|
8337
8419
|
}
|
|
8338
8420
|
}
|
|
8339
|
-
function
|
|
8421
|
+
function descriptionOf(spec) {
|
|
8422
|
+
if (spec.kind === "optional" || spec.kind === "nullable") return descriptionOf(spec.inner);
|
|
8423
|
+
return spec.desc;
|
|
8424
|
+
}
|
|
8425
|
+
function compileUndescribed(z20, spec) {
|
|
8340
8426
|
if (spec.kind === "optional") {
|
|
8341
|
-
return
|
|
8427
|
+
return compileUndescribed(z20, spec.inner).optional();
|
|
8342
8428
|
}
|
|
8343
8429
|
if (spec.kind === "nullable") {
|
|
8344
|
-
return
|
|
8430
|
+
return compileUndescribed(z20, spec.inner).nullable();
|
|
8345
8431
|
}
|
|
8346
|
-
|
|
8347
|
-
return spec.desc === void 0 ? schema : schema.describe(spec.desc);
|
|
8432
|
+
return compileBase(z20, spec);
|
|
8348
8433
|
}
|
|
8349
|
-
function
|
|
8434
|
+
function compileField(z20, spec) {
|
|
8435
|
+
const schema = compileUndescribed(z20, spec);
|
|
8436
|
+
const desc = descriptionOf(spec);
|
|
8437
|
+
return desc === void 0 ? schema : schema.describe(desc);
|
|
8438
|
+
}
|
|
8439
|
+
function compileShape(z20, fields) {
|
|
8350
8440
|
const shape = {};
|
|
8351
8441
|
for (const [key, spec] of Object.entries(fields)) {
|
|
8352
|
-
shape[key] = compileField(
|
|
8442
|
+
shape[key] = compileField(z20, spec);
|
|
8353
8443
|
}
|
|
8354
8444
|
return shape;
|
|
8355
8445
|
}
|
|
@@ -8537,14 +8627,118 @@ var approveAndMergePrContract = defineToolContract({
|
|
|
8537
8627
|
}
|
|
8538
8628
|
}
|
|
8539
8629
|
});
|
|
8630
|
+
var getConnectionContextContract = defineToolContract({
|
|
8631
|
+
name: "get_connection_context",
|
|
8632
|
+
agent: {
|
|
8633
|
+
description: "Resolve WHAT this session is bound to \u2014 call this FIRST, before inferring anything from names or branch strings. Returns the current task (id, title, status, branch, baseBranch, whether it is a pack parent, and its parentTaskId when it is itself a child), its project (id, name), and the acting agent (id, model, mode, isAuto). This is the pod-side half of the same tool the external MCP surface exposes, so a shared skill can open with one call in both places; it reports no user account, because a pod is bound to a card rather than to a person's session.",
|
|
8634
|
+
fields: {}
|
|
8635
|
+
},
|
|
8636
|
+
mcp: {
|
|
8637
|
+
description: "Resolve WHO this connection is and WHAT project/board it points at \u2014 call this FIRST, before inferring anything from names. Returns the effective account, project, and board (sub-project) each with BOTH its immutable ID and its human-readable name/slug, the granted capabilities (read/create/update/chat/files/build), management URLs, and a one-line summary. Removes the ambiguity between a project's canonical name and a board label. Pass projectId to target a specific project; otherwise the configured default project is used.",
|
|
8638
|
+
fields: {
|
|
8639
|
+
projectId: mcpProjectId
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8642
|
+
});
|
|
8540
8643
|
var tasksContracts = [
|
|
8541
8644
|
getTaskContract,
|
|
8542
8645
|
postToChatContract,
|
|
8543
8646
|
readTaskChatContract,
|
|
8544
8647
|
listTagsContract,
|
|
8545
8648
|
searchTasksContract,
|
|
8546
|
-
approveAndMergePrContract
|
|
8649
|
+
approveAndMergePrContract,
|
|
8650
|
+
getConnectionContextContract
|
|
8651
|
+
];
|
|
8652
|
+
var STATUS_ENUM = [
|
|
8653
|
+
"Planning",
|
|
8654
|
+
"Open",
|
|
8655
|
+
"InProgress",
|
|
8656
|
+
"ReviewPR",
|
|
8657
|
+
"ReviewDev",
|
|
8658
|
+
"ReviewLive",
|
|
8659
|
+
"Complete",
|
|
8660
|
+
"Cancelled"
|
|
8547
8661
|
];
|
|
8662
|
+
var RISK_ENUM = ["critical", "high", "medium", "low"];
|
|
8663
|
+
var GITHUB_BRANCH_MEANING = "Record the task's ACTUAL working branch (e.g. a locally-driven pack's branch cut at claim time, so identification never mints a competing name and pack-child merges settle against reality).";
|
|
8664
|
+
var GITHUB_BRANCH_DESC = "Record the task's ACTUAL working branch (e.g. a locally-driven pack's branch cut at claim time, so identification never mints a competing name and pack-child merges settle against reality), or null to detach. The branch must already exist on origin, and a live workspace bound to a different branch rejects the write.";
|
|
8665
|
+
var updateTaskContract = defineToolContract({
|
|
8666
|
+
name: "update_task",
|
|
8667
|
+
agent: {
|
|
8668
|
+
description: "Update the CURRENT task's title, description, plan, githubBranch, or status \u2014 the one tool the shared Conveyor skills use for card writes, so the same skill text works here and in a local MCP session. Pass task_id ONLY with status, to move one of this card's children (claim it InProgress, promote it to Open); every other field always applies to the current task, because the server scopes plan/title writes to this session. Status rides the same path a human or a build uses, so watchdog and board semantics hold \u2014 for an emergency override that skips those, use force_update_task_status.",
|
|
8669
|
+
fields: {
|
|
8670
|
+
title: f.optional(f.string({ desc: "New title" })),
|
|
8671
|
+
description: f.optional(f.string({ desc: cardDescriptionDesc("New description") })),
|
|
8672
|
+
plan: f.optional(f.string({ desc: "New plan (markdown)" })),
|
|
8673
|
+
status: f.optional(f.enum(STATUS_ENUM, { desc: "New status" })),
|
|
8674
|
+
// Not nullable here, unlike the mcp surface: the pod's underlying
|
|
8675
|
+
// `updateTaskProperties` takes `githubBranch?: string` with no null
|
|
8676
|
+
// branch, so a pod can RECORD a branch but cannot detach one. Offering
|
|
8677
|
+
// null would advertise a write the server would silently drop.
|
|
8678
|
+
githubBranch: f.optional(
|
|
8679
|
+
f.string({
|
|
8680
|
+
min: 1,
|
|
8681
|
+
desc: `${GITHUB_BRANCH_MEANING} The pod does NOT validate this: the value is written through with no check that the ref exists on origin, and no guard against repointing the record away from the branch this pod is actually running on (which strands the compute). Push the branch first, and only name a branch this session genuinely owns. Detaching (clearing) a branch is not available from a pod \u2014 use the external MCP surface for that.`
|
|
8682
|
+
})
|
|
8683
|
+
),
|
|
8684
|
+
task_id: f.optional(
|
|
8685
|
+
f.string({
|
|
8686
|
+
// min:1 is load-bearing: "" is falsy, so an empty task_id would slip
|
|
8687
|
+
// past both the refusal below and the child-routing branch, and
|
|
8688
|
+
// silently update the CURRENT card — the exact mis-targeting this
|
|
8689
|
+
// field's guard exists to prevent.
|
|
8690
|
+
min: 1,
|
|
8691
|
+
desc: "Child task ID to move. Valid ONLY alongside status \u2014 pairing it with title/description/plan/githubBranch is rejected rather than silently applied to the current card. Omit to update the current task."
|
|
8692
|
+
})
|
|
8693
|
+
)
|
|
8694
|
+
}
|
|
8695
|
+
},
|
|
8696
|
+
mcp: {
|
|
8697
|
+
description: "Update task fields: title, description, plan, status, risk, story points, assignment, githubBranch, or tags. Set status to claim a card (InProgress), triage it (Open), or cancel it (Cancelled); for review approvals prefer approve_task / request_changes, which guard against stale-state races. Tags are additive/subtractive \u2014 pass addTags/removeTags with tag names (not a replace-set). Pass projectId to target a specific project; otherwise the configured default project is used. Moving a task beyond Planning auto-fills any missing icon, story points, and agent assignment \u2014 don't spend turns on them; pass storyPointValue only to correct the sizing yourself. For subtasks use update_subtask.",
|
|
8698
|
+
fields: {
|
|
8699
|
+
projectId: mcpProjectId,
|
|
8700
|
+
taskId: f.string({ desc: "The task ID" }),
|
|
8701
|
+
title: f.optional(f.string({ desc: "New title" })),
|
|
8702
|
+
description: f.optional(f.string({ desc: cardDescriptionDesc("New description") })),
|
|
8703
|
+
plan: f.optional(f.string({ desc: "New plan (markdown)" })),
|
|
8704
|
+
status: f.optional(f.enum(STATUS_ENUM, { desc: "New status" })),
|
|
8705
|
+
risk: f.optional(
|
|
8706
|
+
f.nullable(
|
|
8707
|
+
f.enum(RISK_ENUM, {
|
|
8708
|
+
desc: "Risk level \u2014 how much important surface the task touches (critical/high/medium/low). Pass null to clear."
|
|
8709
|
+
})
|
|
8710
|
+
)
|
|
8711
|
+
),
|
|
8712
|
+
storyPointValue: f.optional(
|
|
8713
|
+
f.nullable(
|
|
8714
|
+
f.number({
|
|
8715
|
+
desc: `${storyPointValueDesc}. The tiers are per-project \u2014 a value the project has not configured is rejected. Pass null to clear it; a card beyond Planning with no story point is re-filled by identification.`
|
|
8716
|
+
})
|
|
8717
|
+
)
|
|
8718
|
+
),
|
|
8719
|
+
assignedUserId: f.optional(f.nullable(f.string({ desc: "User ID to assign, or null" }))),
|
|
8720
|
+
subProjectId: f.optional(
|
|
8721
|
+
f.nullable(
|
|
8722
|
+
f.string({
|
|
8723
|
+
desc: "Assign the task to a sub-project board; null moves it back to the parent board. Omit to leave unchanged."
|
|
8724
|
+
})
|
|
8725
|
+
)
|
|
8726
|
+
),
|
|
8727
|
+
githubBranch: f.optional(f.nullable(f.string({ desc: GITHUB_BRANCH_DESC }))),
|
|
8728
|
+
addTags: f.optional(
|
|
8729
|
+
f.array(f.string(), {
|
|
8730
|
+
desc: 'Tag names to add to the card (e.g. ["refactor"]). Additive \u2014 existing tags are kept. Unknown names are rejected; use list_tags to see available tags or manage_tags to create one.'
|
|
8731
|
+
})
|
|
8732
|
+
),
|
|
8733
|
+
removeTags: f.optional(
|
|
8734
|
+
f.array(f.string(), {
|
|
8735
|
+
desc: "Tag names to remove from the card. Removing a tag the card doesn't have is a no-op."
|
|
8736
|
+
})
|
|
8737
|
+
)
|
|
8738
|
+
}
|
|
8739
|
+
}
|
|
8740
|
+
});
|
|
8741
|
+
var taskUpdateContracts = [updateTaskContract];
|
|
8548
8742
|
var tagRef = f.string({
|
|
8549
8743
|
desc: "Tag id, or the exact tag name (case-insensitive)",
|
|
8550
8744
|
min: 1,
|
|
@@ -8835,7 +9029,7 @@ var createSubtaskContract = defineToolContract({
|
|
|
8835
9029
|
var updateSubtaskContract = defineToolContract({
|
|
8836
9030
|
name: "update_subtask",
|
|
8837
9031
|
agent: {
|
|
8838
|
-
description: "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue, dependsOn) \u2014 and the sanctioned path to make a child buildable: promote it to status Open, assign its agent (agentIdOrName), and set story points. Setting story points does NOT auto-promote; set status explicitly. For the current task use
|
|
9032
|
+
description: "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue, dependsOn) \u2014 and the sanctioned path to make a child buildable: promote it to status Open, assign its agent (agentIdOrName), and set story points. Setting story points does NOT auto-promote; set status explicitly. For the current task use update_task.",
|
|
8839
9033
|
fields: {
|
|
8840
9034
|
subtaskId: f.string({ desc: "The subtask ID to update" }),
|
|
8841
9035
|
title: f.optional(f.string()),
|
|
@@ -8900,7 +9094,7 @@ var deleteSubtaskContract = defineToolContract({
|
|
|
8900
9094
|
var listSubtasksContract = defineToolContract({
|
|
8901
9095
|
name: "list_subtasks",
|
|
8902
9096
|
agent: {
|
|
8903
|
-
description: "List all subtasks under the current parent task. Default compact view returns per child: status, agent, story points, PR number/state, dependencies
|
|
9097
|
+
description: "List all subtasks under the current parent task. Default compact view returns per child: status, agent, story points, PR number/state, and dependencies. On a FAN-OUT pack it also returns holdsBuildSlot per child plus a packSlots summary (in-flight environments vs the PACK_CHILD_LIMIT cap, and which children hold the slots); both are omitted on the default single-pod path, where one pod implements every child and nothing holds a slot. Use to coordinate child work; pass verbose:true only when you need full description/plan text (large). For non-child tasks use get_task.",
|
|
8904
9098
|
fields: {
|
|
8905
9099
|
verbose: f.optional(
|
|
8906
9100
|
f.boolean({
|
|
@@ -9103,41 +9297,448 @@ var createPullRequestContract = defineToolContract({
|
|
|
9103
9297
|
}
|
|
9104
9298
|
},
|
|
9105
9299
|
mcp: {
|
|
9106
|
-
description: "Open a GitHub pull request for a task's existing branch (the branch must already be pushed to origin). Pass projectId to target a specific project; otherwise the configured default project is used. Moves the task to ReviewPR. Returns the PR number and URL.",
|
|
9107
|
-
fields: {
|
|
9108
|
-
projectId: mcpProjectId,
|
|
9109
|
-
taskId: f.string({ desc: "The task ID whose branch should be opened as a PR" }),
|
|
9110
|
-
title: f.string({ desc: "Pull request title" }),
|
|
9111
|
-
body: f.string({
|
|
9112
|
-
desc: "Pull request body (markdown). For a diff that changes rendered UI, embed the visual proof inline: upload the capture with upload_attachment and paste the downloadUrl it returns as , in addition to leaving it on the card."
|
|
9113
|
-
}),
|
|
9114
|
-
head: f.optional(
|
|
9115
|
-
f.string({ desc: "Source branch for the PR (defaults to the task's branch)" })
|
|
9116
|
-
),
|
|
9117
|
-
base: f.optional(
|
|
9118
|
-
f.string({ desc: "Target branch for the PR (defaults to the repo default)" })
|
|
9119
|
-
)
|
|
9120
|
-
}
|
|
9300
|
+
description: "Open a GitHub pull request for a task's existing branch (the branch must already be pushed to origin). Pass projectId to target a specific project; otherwise the configured default project is used. Moves the task to ReviewPR. Returns the PR number and URL.",
|
|
9301
|
+
fields: {
|
|
9302
|
+
projectId: mcpProjectId,
|
|
9303
|
+
taskId: f.string({ desc: "The task ID whose branch should be opened as a PR" }),
|
|
9304
|
+
title: f.string({ desc: "Pull request title" }),
|
|
9305
|
+
body: f.string({
|
|
9306
|
+
desc: "Pull request body (markdown). For a diff that changes rendered UI, embed the visual proof inline: upload the capture with upload_attachment and paste the downloadUrl it returns as , in addition to leaving it on the card."
|
|
9307
|
+
}),
|
|
9308
|
+
head: f.optional(
|
|
9309
|
+
f.string({ desc: "Source branch for the PR (defaults to the task's branch)" })
|
|
9310
|
+
),
|
|
9311
|
+
base: f.optional(
|
|
9312
|
+
f.string({ desc: "Target branch for the PR (defaults to the repo default)" })
|
|
9313
|
+
)
|
|
9314
|
+
}
|
|
9315
|
+
}
|
|
9316
|
+
});
|
|
9317
|
+
var pullRequestContracts = [createPullRequestContract];
|
|
9318
|
+
var REGISTRY_RULE = "Only channels an admin registered as work channels in project settings are reachable \u2014 an unregistered channel is invisible here no matter what the bot can see in the workspace, and the card-sync channels are NOT automatically included.";
|
|
9319
|
+
var NO_STORAGE = "Messages are fetched live from Slack/Discord on every call and are never stored by Conveyor.";
|
|
9320
|
+
var listProjectIntegrationsContract = defineToolContract({
|
|
9321
|
+
name: "list_project_integrations",
|
|
9322
|
+
agent: {
|
|
9323
|
+
description: `Find out what this project is connected to before assuming a capability exists \u2014 repository forge, Slack or Discord, email, GCP, Grafana, Google Analytics, Google Drive, Cloudflare, and whether incident reporting is set up. Also lists the registered work channels you may read or post in, and how many meetings have been ingested \u2014 so you can tell whether reading meetings is worth doing before calling a meeting tool. Credential-free: every value says whether something is configured, never what the secret is. Call this when you are about to reach for an integration and want to know whether it is there, rather than trying and handling a failure.`,
|
|
9324
|
+
fields: {}
|
|
9325
|
+
},
|
|
9326
|
+
mcp: {
|
|
9327
|
+
description: `Report which integrations a project has configured \u2014 repository forge, Slack/Discord, email, GCP, Grafana, Google Analytics, Google Drive, Cloudflare, incident reporting \u2014 plus the work channels registered for agent access and the project's ingested-meeting count. Returns configuration booleans only, never credential material. Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9328
|
+
fields: {
|
|
9329
|
+
projectId: mcpProjectId
|
|
9330
|
+
}
|
|
9331
|
+
}
|
|
9332
|
+
});
|
|
9333
|
+
var listProjectChannelsContract = defineToolContract({
|
|
9334
|
+
name: "list_project_channels",
|
|
9335
|
+
agent: {
|
|
9336
|
+
description: `List the Slack or Discord channels this project has registered as work channels, each with the admin's description of what happens there and whether you may read it and post into it. Start here before read_channel_messages \u2014 the description is what tells you which channel is worth consulting. ${REGISTRY_RULE}`,
|
|
9337
|
+
fields: {}
|
|
9338
|
+
},
|
|
9339
|
+
mcp: {
|
|
9340
|
+
description: `List a project's registered work channels: provider, channel id and name, the admin's description of what the channel is for, and the allowRead/allowPost grants. ${REGISTRY_RULE} Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9341
|
+
fields: {
|
|
9342
|
+
projectId: mcpProjectId
|
|
9343
|
+
}
|
|
9344
|
+
}
|
|
9345
|
+
});
|
|
9346
|
+
var CHANNEL_ID_DESC = "Provider channel id, exactly as list_project_channels reports it (Slack `C\u2026`, Discord a numeric id). Not the channel name.";
|
|
9347
|
+
var LIMIT_DESC = "How many messages to return, newest first. Defaults to 25, maximum 50. Prefer a small read plus a follow-up over one large one \u2014 channel history spends context fast.";
|
|
9348
|
+
var BEFORE_DESC = "Page further back: return messages older than this cursor. Pass the `olderCursor` from a previous call; a null cursor there means you have reached the start of the channel.";
|
|
9349
|
+
var AFTER_DESC = "Return only messages newer than this cursor. Use to catch up on a channel.";
|
|
9350
|
+
var THREAD_DESC = "Read one thread instead of the channel surface. Pass a `threadTs` seen on a message from a previous read.";
|
|
9351
|
+
var readChannelMessagesContract = defineToolContract({
|
|
9352
|
+
name: "read_channel_messages",
|
|
9353
|
+
agent: {
|
|
9354
|
+
description: `Read recent messages from a registered work channel, newest first \u2014 to catch up on a discussion, find the context behind a decision, or check whether something was already raised. Each message reports its author, whether that author is a bot (Conveyor's own card feed posts show up here too, so check this before treating a message as a teammate's), its text, and a cursor for paging further back. ${REGISTRY_RULE} ${NO_STORAGE}`,
|
|
9355
|
+
fields: {
|
|
9356
|
+
channelId: f.string({ desc: CHANNEL_ID_DESC }),
|
|
9357
|
+
limit: f.optional(f.number({ desc: LIMIT_DESC, int: true, min: 1, max: 50 })),
|
|
9358
|
+
before: f.optional(f.string({ desc: BEFORE_DESC })),
|
|
9359
|
+
after: f.optional(f.string({ desc: AFTER_DESC })),
|
|
9360
|
+
threadTs: f.optional(f.string({ desc: THREAD_DESC }))
|
|
9361
|
+
}
|
|
9362
|
+
},
|
|
9363
|
+
mcp: {
|
|
9364
|
+
description: `Read recent messages from a project's registered work channel, newest first. Each message carries author name, a bot flag, text, a provider-native cursor, and a timestamp; the response carries an olderCursor for paging back. ${REGISTRY_RULE} ${NO_STORAGE} Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9365
|
+
fields: {
|
|
9366
|
+
projectId: mcpProjectId,
|
|
9367
|
+
channelId: f.string({ desc: CHANNEL_ID_DESC }),
|
|
9368
|
+
limit: f.optional(f.number({ desc: LIMIT_DESC, int: true, min: 1, max: 50 })),
|
|
9369
|
+
before: f.optional(f.string({ desc: BEFORE_DESC })),
|
|
9370
|
+
after: f.optional(f.string({ desc: AFTER_DESC })),
|
|
9371
|
+
threadTs: f.optional(f.string({ desc: THREAD_DESC }))
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
});
|
|
9375
|
+
var POST_TEXT_DESC = "What to say, in plain text or light markdown. An attribution footer naming you is appended automatically, so do not sign the message yourself. Channel-wide pings (@everyone, @here, <!channel>) are rendered as ordinary text and will not notify anyone.";
|
|
9376
|
+
var POST_THREAD_DESC = "Reply inside a thread rather than to the channel. Prefer this when responding to a specific message \u2014 pass the `threadTs` from the message you are answering.";
|
|
9377
|
+
var POST_WARNING = "This is visible to everyone in the channel and cannot be edited or deleted afterwards. Posting requires a per-channel grant that is OFF by default, separate from read access, so most registered channels will refuse it.";
|
|
9378
|
+
var postChannelMessageContract = defineToolContract({
|
|
9379
|
+
name: "post_channel_message",
|
|
9380
|
+
agent: {
|
|
9381
|
+
description: `Post a message into a registered work channel \u2014 to answer a question aimed at you, report something the team is waiting on, or ask for input you genuinely need. ${POST_WARNING} ${REGISTRY_RULE}`,
|
|
9382
|
+
fields: {
|
|
9383
|
+
channelId: f.string({ desc: CHANNEL_ID_DESC }),
|
|
9384
|
+
text: f.string({ desc: POST_TEXT_DESC, min: 1, max: 1800 }),
|
|
9385
|
+
threadTs: f.optional(f.string({ desc: POST_THREAD_DESC }))
|
|
9386
|
+
}
|
|
9387
|
+
},
|
|
9388
|
+
mcp: {
|
|
9389
|
+
description: `Post a message into a project's registered work channel, attributed to the acting agent or user. ${POST_WARNING} ${REGISTRY_RULE} Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9390
|
+
fields: {
|
|
9391
|
+
projectId: mcpProjectId,
|
|
9392
|
+
channelId: f.string({ desc: CHANNEL_ID_DESC }),
|
|
9393
|
+
text: f.string({ desc: POST_TEXT_DESC, min: 1, max: 1800 }),
|
|
9394
|
+
threadTs: f.optional(f.string({ desc: POST_THREAD_DESC }))
|
|
9395
|
+
}
|
|
9396
|
+
}
|
|
9397
|
+
});
|
|
9398
|
+
var ANALYTICS_RANGE = "How many days back to summarize, ending today. Defaults to 28; 90 is the maximum, because GA4 retention makes longer windows unreliable.";
|
|
9399
|
+
var ANALYTICS_CAMPAIGN = "Restrict to one campaign name. The campaigns breakdown is always unfiltered, so read it first to see what exists.";
|
|
9400
|
+
var ANALYTICS_SHAPE = "Returns headline totals (sessions, active users, new users, page views, average session duration, bounce rate) plus the top five pages, traffic sources, landing pages and campaigns. A project that has not set up GA4 answers configured:false with a reason rather than failing \u2014 that is a normal state, not an error.";
|
|
9401
|
+
var getAnalyticsSummaryContract = defineToolContract({
|
|
9402
|
+
name: "get_analytics_summary",
|
|
9403
|
+
agent: {
|
|
9404
|
+
description: `Read this project's Google Analytics 4 traffic \u2014 real numbers for questions like "did that launch land" or "which pages actually get read", instead of guessing. ${ANALYTICS_SHAPE}`,
|
|
9405
|
+
fields: {
|
|
9406
|
+
rangeDays: f.optional(f.number({ desc: ANALYTICS_RANGE, int: true, min: 1, max: 90 })),
|
|
9407
|
+
campaign: f.optional(f.string({ desc: ANALYTICS_CAMPAIGN, max: 200 }))
|
|
9408
|
+
}
|
|
9409
|
+
},
|
|
9410
|
+
mcp: {
|
|
9411
|
+
description: `Read a project's Google Analytics 4 traffic summary. ${ANALYTICS_SHAPE} Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9412
|
+
fields: {
|
|
9413
|
+
projectId: mcpProjectId,
|
|
9414
|
+
rangeDays: f.optional(f.number({ desc: ANALYTICS_RANGE, int: true, min: 1, max: 90 })),
|
|
9415
|
+
campaign: f.optional(f.string({ desc: ANALYTICS_CAMPAIGN, max: 200 }))
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
});
|
|
9419
|
+
var integrationsContracts = [
|
|
9420
|
+
listProjectIntegrationsContract,
|
|
9421
|
+
listProjectChannelsContract,
|
|
9422
|
+
readChannelMessagesContract,
|
|
9423
|
+
postChannelMessageContract,
|
|
9424
|
+
getAnalyticsSummaryContract
|
|
9425
|
+
];
|
|
9426
|
+
var MAX_CONTENT_CHARS = 1e6;
|
|
9427
|
+
var FILE_ID = "Drive file id, as returned by drive_list_files";
|
|
9428
|
+
var ROOT_DEFAULT = "Defaults to the project's connected root folder.";
|
|
9429
|
+
var MCP_PROJECT_TAIL = "Pass projectId to target a specific project; otherwise the configured default project is used.";
|
|
9430
|
+
var driveListFilesContract = defineToolContract({
|
|
9431
|
+
name: "drive_list_files",
|
|
9432
|
+
agent: {
|
|
9433
|
+
description: "List files and folders in the project's connected Google Drive folder. Omit folderId to list the project's root folder. Returns id, name, mimeType, size, and modified time for each entry.",
|
|
9434
|
+
fields: {
|
|
9435
|
+
folderId: f.optional(f.string({ desc: `Folder to list. ${ROOT_DEFAULT}` })),
|
|
9436
|
+
search: f.optional(f.string({ desc: "Only return names containing this text", max: 200 })),
|
|
9437
|
+
limit: f.optional(f.number({ desc: "Max entries (default 100)", int: true, min: 1, max: 200 }))
|
|
9438
|
+
}
|
|
9439
|
+
},
|
|
9440
|
+
mcp: {
|
|
9441
|
+
description: `List files and folders in a project's connected Google Drive folder. Omit folderId to list the project's root folder. Returns id, name, mimeType, size, and modified time for each entry. ${MCP_PROJECT_TAIL}`,
|
|
9442
|
+
fields: {
|
|
9443
|
+
projectId: mcpProjectId,
|
|
9444
|
+
folderId: f.optional(f.string({ desc: `Folder to list. ${ROOT_DEFAULT}` })),
|
|
9445
|
+
search: f.optional(f.string({ desc: "Only return names containing this text", max: 200 })),
|
|
9446
|
+
limit: f.optional(f.number({ desc: "Max entries (default 100)", int: true, min: 1, max: 200 }))
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
9449
|
+
});
|
|
9450
|
+
var driveReadFileContract = defineToolContract({
|
|
9451
|
+
name: "drive_read_file",
|
|
9452
|
+
agent: {
|
|
9453
|
+
description: "Read a file's text content from the project's connected Google Drive folder. Google Docs, Sheets, and Slides are exported to text automatically. Content over 100 KB is truncated.",
|
|
9454
|
+
fields: { fileId: f.string({ desc: FILE_ID }) }
|
|
9455
|
+
},
|
|
9456
|
+
mcp: {
|
|
9457
|
+
description: `Read a file's text content from a project's connected Google Drive folder. Google Docs, Sheets, and Slides are exported to text automatically. Content over 100 KB is truncated. ${MCP_PROJECT_TAIL}`,
|
|
9458
|
+
fields: { projectId: mcpProjectId, fileId: f.string({ desc: FILE_ID }) }
|
|
9459
|
+
}
|
|
9460
|
+
});
|
|
9461
|
+
var driveCreateFileContract = defineToolContract({
|
|
9462
|
+
name: "drive_create_file",
|
|
9463
|
+
agent: {
|
|
9464
|
+
description: "Create a new file in the project's connected Google Drive folder. Use drive_update_file to change an existing file instead.",
|
|
9465
|
+
fields: {
|
|
9466
|
+
name: f.string({ desc: "File name, without any path separators", min: 1, max: 255 }),
|
|
9467
|
+
content: f.string({ desc: "File content, UTF-8 text", max: MAX_CONTENT_CHARS }),
|
|
9468
|
+
mimeType: f.optional(f.string({ desc: "MIME type (default text/plain)" })),
|
|
9469
|
+
folderId: f.optional(f.string({ desc: `Destination folder. ${ROOT_DEFAULT}` }))
|
|
9470
|
+
}
|
|
9471
|
+
},
|
|
9472
|
+
mcp: {
|
|
9473
|
+
description: `Create a new file in a project's connected Google Drive folder. Use drive_update_file to change an existing file instead. ${MCP_PROJECT_TAIL}`,
|
|
9474
|
+
fields: {
|
|
9475
|
+
projectId: mcpProjectId,
|
|
9476
|
+
name: f.string({ desc: "File name, without any path separators", min: 1, max: 255 }),
|
|
9477
|
+
content: f.string({ desc: "File content, UTF-8 text", max: MAX_CONTENT_CHARS }),
|
|
9478
|
+
mimeType: f.optional(f.string({ desc: "MIME type (default text/plain)" })),
|
|
9479
|
+
folderId: f.optional(f.string({ desc: `Destination folder. ${ROOT_DEFAULT}` }))
|
|
9480
|
+
}
|
|
9481
|
+
}
|
|
9482
|
+
});
|
|
9483
|
+
var driveUpdateFileContract = defineToolContract({
|
|
9484
|
+
name: "drive_update_file",
|
|
9485
|
+
agent: {
|
|
9486
|
+
description: "Replace the content of an existing file in the project's connected Google Drive folder. This overwrites the whole file. Google-native documents cannot be overwritten.",
|
|
9487
|
+
fields: {
|
|
9488
|
+
fileId: f.string({ desc: FILE_ID }),
|
|
9489
|
+
content: f.string({ desc: "Replacement content, UTF-8 text", max: MAX_CONTENT_CHARS }),
|
|
9490
|
+
mimeType: f.optional(f.string({ desc: "MIME type (defaults to the file's current type)" }))
|
|
9491
|
+
}
|
|
9492
|
+
},
|
|
9493
|
+
mcp: {
|
|
9494
|
+
description: `Replace the content of an existing file in a project's connected Google Drive folder. This overwrites the whole file \u2014 read it first if you are editing rather than replacing. Google-native documents cannot be overwritten. ${MCP_PROJECT_TAIL}`,
|
|
9495
|
+
fields: {
|
|
9496
|
+
projectId: mcpProjectId,
|
|
9497
|
+
fileId: f.string({ desc: FILE_ID }),
|
|
9498
|
+
content: f.string({ desc: "Replacement content, UTF-8 text", max: MAX_CONTENT_CHARS }),
|
|
9499
|
+
mimeType: f.optional(f.string({ desc: "MIME type (defaults to the file's current type)" }))
|
|
9500
|
+
}
|
|
9501
|
+
}
|
|
9502
|
+
});
|
|
9503
|
+
var driveDeleteFileContract = defineToolContract({
|
|
9504
|
+
name: "drive_delete_file",
|
|
9505
|
+
agent: {
|
|
9506
|
+
description: "Move a file in the project's connected Google Drive folder to the Drive trash. The file is recoverable from the trash; it is never permanently deleted.",
|
|
9507
|
+
fields: { fileId: f.string({ desc: FILE_ID }) }
|
|
9508
|
+
},
|
|
9509
|
+
mcp: {
|
|
9510
|
+
description: `Move a file in a project's connected Google Drive folder to the Drive trash. The file is recoverable from the trash; it is never permanently deleted. ${MCP_PROJECT_TAIL}`,
|
|
9511
|
+
fields: { projectId: mcpProjectId, fileId: f.string({ desc: FILE_ID }) }
|
|
9512
|
+
}
|
|
9513
|
+
});
|
|
9514
|
+
var driveCreateFolderContract = defineToolContract({
|
|
9515
|
+
name: "drive_create_folder",
|
|
9516
|
+
agent: {
|
|
9517
|
+
description: "Create a folder inside the project's connected Google Drive folder.",
|
|
9518
|
+
fields: {
|
|
9519
|
+
name: f.string({ desc: "Folder name, without any path separators", min: 1, max: 255 }),
|
|
9520
|
+
folderId: f.optional(f.string({ desc: `Parent folder. ${ROOT_DEFAULT}` }))
|
|
9521
|
+
}
|
|
9522
|
+
},
|
|
9523
|
+
mcp: {
|
|
9524
|
+
description: `Create a folder inside a project's connected Google Drive folder. ${MCP_PROJECT_TAIL}`,
|
|
9525
|
+
fields: {
|
|
9526
|
+
projectId: mcpProjectId,
|
|
9527
|
+
name: f.string({ desc: "Folder name, without any path separators", min: 1, max: 255 }),
|
|
9528
|
+
folderId: f.optional(f.string({ desc: `Parent folder. ${ROOT_DEFAULT}` }))
|
|
9529
|
+
}
|
|
9530
|
+
}
|
|
9531
|
+
});
|
|
9532
|
+
var driveContracts = [
|
|
9533
|
+
driveListFilesContract,
|
|
9534
|
+
driveReadFileContract,
|
|
9535
|
+
driveCreateFileContract,
|
|
9536
|
+
driveUpdateFileContract,
|
|
9537
|
+
driveDeleteFileContract,
|
|
9538
|
+
driveCreateFolderContract
|
|
9539
|
+
];
|
|
9540
|
+
var MEETING_ID = "Meeting id, as returned by list_meetings.";
|
|
9541
|
+
var MCP_TAIL = "Pass projectId to target a specific project; otherwise the configured default project is used.";
|
|
9542
|
+
var SUMMARY_NOTE = 'A meeting whose summary is still being written reports status "processing" and a null summary \u2014 that is normal shortly after ingestion, not an error.';
|
|
9543
|
+
var listMeetingsContract = defineToolContract({
|
|
9544
|
+
name: "list_meetings",
|
|
9545
|
+
agent: {
|
|
9546
|
+
description: `List this project's meetings, newest first \u2014 each with its title, date, source, status, participants, and a short summary preview. Start here when you are asked about "the meeting", "what did we decide", or "what came out of that call". ${SUMMARY_NOTE}`,
|
|
9547
|
+
fields: {
|
|
9548
|
+
limit: f.optional(
|
|
9549
|
+
f.number({ desc: "How many to return (default 20, maximum 50).", int: true, min: 1, max: 50 })
|
|
9550
|
+
),
|
|
9551
|
+
search: f.optional(
|
|
9552
|
+
f.string({ desc: "Case-insensitive match on the meeting title.", max: 200 })
|
|
9553
|
+
)
|
|
9554
|
+
}
|
|
9555
|
+
},
|
|
9556
|
+
mcp: {
|
|
9557
|
+
description: `List a project's meetings, newest first, with title, date, source, status, participants, and a summary preview. ${SUMMARY_NOTE} ${MCP_TAIL}`,
|
|
9558
|
+
fields: {
|
|
9559
|
+
projectId: mcpProjectId,
|
|
9560
|
+
limit: f.optional(
|
|
9561
|
+
f.number({ desc: "How many to return (default 20, maximum 50).", int: true, min: 1, max: 50 })
|
|
9562
|
+
),
|
|
9563
|
+
search: f.optional(
|
|
9564
|
+
f.string({ desc: "Case-insensitive match on the meeting title.", max: 200 })
|
|
9565
|
+
)
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
});
|
|
9569
|
+
var getMeetingContract = defineToolContract({
|
|
9570
|
+
name: "get_meeting",
|
|
9571
|
+
agent: {
|
|
9572
|
+
description: `Read one meeting's details and its FULL AI summary \u2014 the overview, the decisions, and the proposed next steps. **Read this before reaching for the transcript**: the summary usually answers the question, and a transcript costs far more context. Also returns the participant list, the segment count, and the meeting's web link. ${SUMMARY_NOTE}`,
|
|
9573
|
+
fields: { meetingId: f.string({ desc: MEETING_ID }) }
|
|
9574
|
+
},
|
|
9575
|
+
mcp: {
|
|
9576
|
+
description: `Read one meeting's details and full AI summary (overview, decisions, proposed next steps), plus participants, segment count, and its web link. Prefer this over the transcript \u2014 the summary usually answers the question at a fraction of the context. ${SUMMARY_NOTE} ${MCP_TAIL}`,
|
|
9577
|
+
fields: { projectId: mcpProjectId, meetingId: f.string({ desc: MEETING_ID }) }
|
|
9578
|
+
}
|
|
9579
|
+
});
|
|
9580
|
+
var OFFSET_DESC = "Segment index to start from (default 0). Pass the nextOffset from the previous page to continue.";
|
|
9581
|
+
var LIMIT_DESC2 = "Segments per page (default 200, maximum 500). A long meeting runs to thousands, so page it rather than asking for everything.";
|
|
9582
|
+
var readMeetingTranscriptContract = defineToolContract({
|
|
9583
|
+
name: "read_meeting_transcript",
|
|
9584
|
+
agent: {
|
|
9585
|
+
description: `Read a page of a meeting's raw transcript as \`[hh:mm:ss] Speaker: text\` lines. Use this when you need someone's exact words \u2014 a quote, a caveat, or the reasoning behind a decision the summary only states. For "what happened in the meeting", read get_meeting instead. The response carries nextOffset when more segments remain.`,
|
|
9586
|
+
fields: {
|
|
9587
|
+
meetingId: f.string({ desc: MEETING_ID }),
|
|
9588
|
+
offset: f.optional(f.number({ desc: OFFSET_DESC, int: true, min: 0 })),
|
|
9589
|
+
limit: f.optional(f.number({ desc: LIMIT_DESC2, int: true, min: 1, max: 500 }))
|
|
9590
|
+
}
|
|
9591
|
+
},
|
|
9592
|
+
mcp: {
|
|
9593
|
+
description: `Read a page of a meeting's raw transcript as \`[hh:mm:ss] Speaker: text\` lines. Use it for exact wording; prefer get_meeting's summary for what happened. The response carries nextOffset when more segments remain. ${MCP_TAIL}`,
|
|
9594
|
+
fields: {
|
|
9595
|
+
projectId: mcpProjectId,
|
|
9596
|
+
meetingId: f.string({ desc: MEETING_ID }),
|
|
9597
|
+
offset: f.optional(f.number({ desc: OFFSET_DESC, int: true, min: 0 })),
|
|
9598
|
+
limit: f.optional(f.number({ desc: LIMIT_DESC2, int: true, min: 1, max: 500 }))
|
|
9599
|
+
}
|
|
9600
|
+
}
|
|
9601
|
+
});
|
|
9602
|
+
var meetingsContracts = [
|
|
9603
|
+
listMeetingsContract,
|
|
9604
|
+
getMeetingContract,
|
|
9605
|
+
readMeetingTranscriptContract
|
|
9606
|
+
];
|
|
9607
|
+
var SINCE_MINUTES = f.optional(
|
|
9608
|
+
f.number({
|
|
9609
|
+
desc: "Relative time window ending now, in minutes (default 60). Ignored if startTime is set.",
|
|
9610
|
+
int: true,
|
|
9611
|
+
min: 1,
|
|
9612
|
+
max: 10080
|
|
9613
|
+
})
|
|
9614
|
+
);
|
|
9615
|
+
var START_TIME = f.optional(
|
|
9616
|
+
f.string({ desc: "ISO 8601 lower bound (overrides sinceMinutes)" })
|
|
9617
|
+
);
|
|
9618
|
+
var END_TIME = f.optional(f.string({ desc: "ISO 8601 upper bound (default now)" }));
|
|
9619
|
+
var LIMIT = f.optional(
|
|
9620
|
+
f.number({ desc: "Max entries per page (default 50)", int: true, min: 1, max: 200 })
|
|
9621
|
+
);
|
|
9622
|
+
var RAW_QUERY_MAX = 2e3;
|
|
9623
|
+
var gcpFields = {
|
|
9624
|
+
env: f.optional(
|
|
9625
|
+
f.enum(["prod", "dev", "claudespace"], {
|
|
9626
|
+
desc: "GCP environment slot to query (default prod)"
|
|
9627
|
+
})
|
|
9628
|
+
),
|
|
9629
|
+
sinceMinutes: SINCE_MINUTES,
|
|
9630
|
+
startTime: START_TIME,
|
|
9631
|
+
endTime: END_TIME,
|
|
9632
|
+
severity: f.optional(
|
|
9633
|
+
f.enum(SEVERITY_ENUM, {
|
|
9634
|
+
desc: "Minimum severity, inclusive \u2014 ERROR returns ERROR and above"
|
|
9635
|
+
})
|
|
9636
|
+
),
|
|
9637
|
+
services: f.optional(
|
|
9638
|
+
f.array(f.string(), {
|
|
9639
|
+
desc: "Restrict to these Cloud Run service names (prod/dev only). Defaults to all services linked in project settings."
|
|
9640
|
+
})
|
|
9641
|
+
),
|
|
9642
|
+
sqlInstances: f.optional(
|
|
9643
|
+
f.array(f.string(), { desc: "Restrict to these Cloud SQL instance names (prod/dev only)" })
|
|
9644
|
+
),
|
|
9645
|
+
allServices: f.optional(
|
|
9646
|
+
f.boolean({
|
|
9647
|
+
desc: "Set true to search ALL logs in the GCP project, ignoring the linked-resource scope"
|
|
9648
|
+
})
|
|
9649
|
+
),
|
|
9650
|
+
search: f.optional(
|
|
9651
|
+
f.string({ desc: "Free-text search across all log fields (exact substring, not regex)", max: 256 })
|
|
9652
|
+
),
|
|
9653
|
+
filter: f.optional(
|
|
9654
|
+
f.string({
|
|
9655
|
+
desc: "Advanced: raw Cloud Logging filter expression, ANDed with the scope (it cannot widen it)",
|
|
9656
|
+
max: RAW_QUERY_MAX
|
|
9657
|
+
})
|
|
9658
|
+
),
|
|
9659
|
+
limit: LIMIT,
|
|
9660
|
+
pageToken: f.optional(
|
|
9661
|
+
f.string({ desc: "Opaque token from a previous response to fetch the next page" })
|
|
9662
|
+
)
|
|
9663
|
+
};
|
|
9664
|
+
var GCP_SHARED_DESC = "Envs: 'prod' and 'dev' are the project's Cloud Run apps + Cloud SQL databases (scoped by default to the resources linked in project settings); 'claudespace' is the project's GKE agent-pod namespace. Start broad (severity=ERROR, sinceMinutes=60), then narrow with services/search. Returns compact lines: '<time> <SEVERITY> [<source>] <message> | key=value \u2026' (the key=value tail is the entry's structured payload \u2014 error details, service/method, actor and entity ids). When the response ends with a pageToken line, pass that token back as pageToken for the next page.";
|
|
9665
|
+
var queryGcpLogsContract = defineToolContract({
|
|
9666
|
+
name: "query_gcp_logs",
|
|
9667
|
+
agent: {
|
|
9668
|
+
description: `Query Google Cloud Logging for this project's linked GCP environments \u2014 use this to investigate a production or dev failure directly rather than guessing from the code. ${GCP_SHARED_DESC}`,
|
|
9669
|
+
fields: gcpFields
|
|
9670
|
+
},
|
|
9671
|
+
mcp: {
|
|
9672
|
+
description: `Query Google Cloud Logging for a project's linked GCP environments \u2014 use this to investigate production or dev issues directly ('something broke on prod'). ${GCP_SHARED_DESC} Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9673
|
+
fields: { projectId: mcpProjectId, ...gcpFields }
|
|
9121
9674
|
}
|
|
9122
9675
|
});
|
|
9123
|
-
var
|
|
9676
|
+
var grafanaFields = {
|
|
9677
|
+
env: f.optional(
|
|
9678
|
+
f.enum(["prod", "dev"], {
|
|
9679
|
+
desc: "Configured Grafana env mapping to scope by (default prod)"
|
|
9680
|
+
})
|
|
9681
|
+
),
|
|
9682
|
+
sinceMinutes: SINCE_MINUTES,
|
|
9683
|
+
startTime: START_TIME,
|
|
9684
|
+
endTime: END_TIME,
|
|
9685
|
+
level: f.optional(
|
|
9686
|
+
f.enum(["debug", "info", "warn", "error", "fatal"], {
|
|
9687
|
+
desc: "Minimum severity, inclusive \u2014 error returns error and above"
|
|
9688
|
+
})
|
|
9689
|
+
),
|
|
9690
|
+
services: f.optional(
|
|
9691
|
+
f.array(f.string(), { desc: "Restrict to these service_name label values" })
|
|
9692
|
+
),
|
|
9693
|
+
search: f.optional(
|
|
9694
|
+
f.string({ desc: "Substring line filter (exact substring, not regex)", max: 256 })
|
|
9695
|
+
),
|
|
9696
|
+
logql: f.optional(
|
|
9697
|
+
f.string({
|
|
9698
|
+
desc: "Advanced: raw LogQL query \u2014 REPLACES env/services/level/search composition",
|
|
9699
|
+
max: RAW_QUERY_MAX
|
|
9700
|
+
})
|
|
9701
|
+
),
|
|
9702
|
+
limit: LIMIT
|
|
9703
|
+
};
|
|
9704
|
+
var GRAFANA_SHARED_DESC = "the APPLICATION logs shipped to Grafana Cloud/Loki, complementing query_gcp_logs (GCP infrastructure logs). Start with structured filters (env, level=error, sinceMinutes=60, services), then narrow with search; pass raw LogQL via logql only when structured filters cannot express the query (it REPLACES them). The response header echoes the composed LogQL \u2014 iterate on it. Returns compact lines: '<time> <SEVERITY> [<service>] <message>'.";
|
|
9705
|
+
var queryGrafanaLogsContract = defineToolContract({
|
|
9706
|
+
name: "query_grafana_logs",
|
|
9707
|
+
agent: {
|
|
9708
|
+
description: `Query this project's connected Grafana (Loki) logs \u2014 ${GRAFANA_SHARED_DESC}`,
|
|
9709
|
+
fields: grafanaFields
|
|
9710
|
+
},
|
|
9711
|
+
mcp: {
|
|
9712
|
+
description: `Query the project's connected Grafana (Loki) logs \u2014 ${GRAFANA_SHARED_DESC} Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
9713
|
+
fields: { projectId: mcpProjectId, ...grafanaFields }
|
|
9714
|
+
}
|
|
9715
|
+
});
|
|
9716
|
+
var logsContracts = [
|
|
9717
|
+
queryGcpLogsContract,
|
|
9718
|
+
queryGrafanaLogsContract
|
|
9719
|
+
];
|
|
9124
9720
|
var TOOL_CONTRACTS = Object.fromEntries(
|
|
9125
9721
|
[
|
|
9126
9722
|
...tasksContracts,
|
|
9723
|
+
...taskUpdateContracts,
|
|
9127
9724
|
...tagsContracts,
|
|
9128
9725
|
...checklistContracts,
|
|
9129
9726
|
...dependenciesContracts,
|
|
9130
9727
|
...subtasksContracts,
|
|
9131
9728
|
...attachmentsContracts,
|
|
9132
9729
|
...suggestionsContracts,
|
|
9133
|
-
...pullRequestContracts
|
|
9730
|
+
...pullRequestContracts,
|
|
9731
|
+
...integrationsContracts,
|
|
9732
|
+
...driveContracts,
|
|
9733
|
+
...meetingsContracts,
|
|
9734
|
+
...logsContracts
|
|
9134
9735
|
].map((contract) => [contract.name, contract])
|
|
9135
9736
|
);
|
|
9136
9737
|
|
|
9137
9738
|
// src/tools/contract-tool.ts
|
|
9138
|
-
import { z as
|
|
9739
|
+
import { z as z12 } from "zod";
|
|
9139
9740
|
function agentShape(surface) {
|
|
9140
|
-
return compileShape(
|
|
9741
|
+
return compileShape(z12, surface.fields);
|
|
9141
9742
|
}
|
|
9142
9743
|
function defineContractTool(contract, handler, options) {
|
|
9143
9744
|
return {
|
|
@@ -9222,6 +9823,54 @@ ${plan}` : plan
|
|
|
9222
9823
|
{ annotations: { readOnlyHint: true } }
|
|
9223
9824
|
);
|
|
9224
9825
|
}
|
|
9826
|
+
function buildGetConnectionContextTool(connection) {
|
|
9827
|
+
return defineContractTool(
|
|
9828
|
+
getConnectionContextContract,
|
|
9829
|
+
async () => {
|
|
9830
|
+
try {
|
|
9831
|
+
const ctx = await connection.call("getTaskContext", {
|
|
9832
|
+
sessionId: connection.sessionId,
|
|
9833
|
+
peekPlanRevision: true
|
|
9834
|
+
});
|
|
9835
|
+
return textResult(
|
|
9836
|
+
JSON.stringify(
|
|
9837
|
+
{
|
|
9838
|
+
task: {
|
|
9839
|
+
id: ctx.id,
|
|
9840
|
+
title: ctx.title,
|
|
9841
|
+
status: ctx.status,
|
|
9842
|
+
branch: ctx.githubBranch,
|
|
9843
|
+
baseBranch: ctx.baseBranch,
|
|
9844
|
+
isParentTask: ctx.isParentTask ?? false,
|
|
9845
|
+
parentTaskId: ctx.parentTaskId,
|
|
9846
|
+
// Safe to report BECAUSE we peeked: the marker is still there
|
|
9847
|
+
// for get_current_plan to consume and banner properly.
|
|
9848
|
+
planRevisedAt: ctx.planRevisedAt ?? null
|
|
9849
|
+
},
|
|
9850
|
+
project: { id: ctx.projectId, name: ctx.projectName ?? null },
|
|
9851
|
+
agent: {
|
|
9852
|
+
id: ctx.agentId,
|
|
9853
|
+
model: ctx.model,
|
|
9854
|
+
mode: ctx.agentMode ?? null,
|
|
9855
|
+
isAuto: ctx.isAuto ?? false
|
|
9856
|
+
},
|
|
9857
|
+
// Said plainly so a shared skill does not go looking for an
|
|
9858
|
+
// account here: a pod is bound to a card, not to a person.
|
|
9859
|
+
summary: `Pod session bound to task ${ctx.id} on branch ${ctx.githubBranch ?? "(none yet)"} (base ${ctx.baseBranch ?? "(none)"}) in project ${ctx.projectName ?? ctx.projectId}. No user account is attached to a pod session.`
|
|
9860
|
+
},
|
|
9861
|
+
null,
|
|
9862
|
+
2
|
|
9863
|
+
)
|
|
9864
|
+
);
|
|
9865
|
+
} catch (error) {
|
|
9866
|
+
return textResult(
|
|
9867
|
+
`Failed to resolve connection context: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
9868
|
+
);
|
|
9869
|
+
}
|
|
9870
|
+
},
|
|
9871
|
+
{ annotations: { readOnlyHint: true } }
|
|
9872
|
+
);
|
|
9873
|
+
}
|
|
9225
9874
|
function buildGetTaskTool(connection) {
|
|
9226
9875
|
return defineContractTool(
|
|
9227
9876
|
getTaskContract,
|
|
@@ -9246,11 +9895,11 @@ function buildGetExecutionLogsTool(connection) {
|
|
|
9246
9895
|
"get_execution_logs",
|
|
9247
9896
|
"Read CLI execution logs \u2014 agent reasoning, tool calls, and setup/dev-server output. Filter via source='agent' or 'application'. For human chat use read_task_chat.",
|
|
9248
9897
|
{
|
|
9249
|
-
task_id:
|
|
9898
|
+
task_id: z13.string().optional().describe(
|
|
9250
9899
|
"Task ID or slug. Omit to read logs from the current task. Only the current task or one of its child tasks can be read."
|
|
9251
9900
|
),
|
|
9252
|
-
source:
|
|
9253
|
-
limit:
|
|
9901
|
+
source: z13.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
|
|
9902
|
+
limit: z13.number().optional().describe("Max number of log entries to return (default 50, max 500).")
|
|
9254
9903
|
},
|
|
9255
9904
|
async ({ task_id, source, limit }) => {
|
|
9256
9905
|
try {
|
|
@@ -9336,6 +9985,7 @@ function buildTaskContextTools(connection) {
|
|
|
9336
9985
|
return [
|
|
9337
9986
|
buildReadTaskChatTool(connection),
|
|
9338
9987
|
buildGetCurrentPlanTool(connection),
|
|
9988
|
+
buildGetConnectionContextTool(connection),
|
|
9339
9989
|
buildGetTaskTool(connection),
|
|
9340
9990
|
buildGetExecutionLogsTool(connection),
|
|
9341
9991
|
buildListTaskFilesTool(connection),
|
|
@@ -9344,7 +9994,7 @@ function buildTaskContextTools(connection) {
|
|
|
9344
9994
|
}
|
|
9345
9995
|
|
|
9346
9996
|
// src/tools/dependency-suggestion-tools.ts
|
|
9347
|
-
import { z as
|
|
9997
|
+
import { z as z14 } from "zod";
|
|
9348
9998
|
function buildGetDependenciesTool(connection) {
|
|
9349
9999
|
return defineContractTool(
|
|
9350
10000
|
getDependenciesContract,
|
|
@@ -9368,10 +10018,10 @@ function buildGetSuggestionsTool(connection) {
|
|
|
9368
10018
|
"get_suggestions",
|
|
9369
10019
|
"List project suggestions sorted by vote score. Filter by status or cap with limit (default 20). Suggestions are project-level ideas, not tasks \u2014 use get_task for tasks.",
|
|
9370
10020
|
{
|
|
9371
|
-
status:
|
|
10021
|
+
status: z14.string().optional().describe(
|
|
9372
10022
|
"Filter by status: Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled"
|
|
9373
10023
|
),
|
|
9374
|
-
limit:
|
|
10024
|
+
limit: z14.number().int().min(1).max(100).optional().describe("Max results (default 20)")
|
|
9375
10025
|
},
|
|
9376
10026
|
async ({ status, limit }) => {
|
|
9377
10027
|
try {
|
|
@@ -9395,7 +10045,7 @@ function buildGetSuggestionsTool(connection) {
|
|
|
9395
10045
|
}
|
|
9396
10046
|
|
|
9397
10047
|
// src/tools/mutation-tools.ts
|
|
9398
|
-
import { z as
|
|
10048
|
+
import { z as z15 } from "zod";
|
|
9399
10049
|
|
|
9400
10050
|
// src/runner/refresh-verify-heal.ts
|
|
9401
10051
|
async function refreshAndVerifyGithubCredential(cwd, mint) {
|
|
@@ -9527,7 +10177,7 @@ function buildForceUpdateTaskStatusTool(connection) {
|
|
|
9527
10177
|
"force_update_task_status",
|
|
9528
10178
|
"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.",
|
|
9529
10179
|
{
|
|
9530
|
-
status:
|
|
10180
|
+
status: z15.enum([
|
|
9531
10181
|
"Planning",
|
|
9532
10182
|
"Open",
|
|
9533
10183
|
"InProgress",
|
|
@@ -9537,7 +10187,7 @@ function buildForceUpdateTaskStatusTool(connection) {
|
|
|
9537
10187
|
"Complete",
|
|
9538
10188
|
"Cancelled"
|
|
9539
10189
|
]).describe("The new status for the task"),
|
|
9540
|
-
task_id:
|
|
10190
|
+
task_id: z15.string().optional().describe("Child task ID to update. Omit to update the current task.")
|
|
9541
10191
|
},
|
|
9542
10192
|
async ({ status, task_id }) => {
|
|
9543
10193
|
try {
|
|
@@ -9690,10 +10340,10 @@ function buildCreateFollowUpTaskTool(connection) {
|
|
|
9690
10340
|
"create_follow_up_task",
|
|
9691
10341
|
"Create a follow-up task that depends on the current task. The new card is a SIBLING of this one (same parent) and is blocked until this task merges \u2014 it is NOT a child of this card. To break this card into child cards that build as a pack, use create_subtask. For blockers use add_dependency.",
|
|
9692
10342
|
{
|
|
9693
|
-
title:
|
|
9694
|
-
description:
|
|
9695
|
-
plan:
|
|
9696
|
-
story_point_value:
|
|
10343
|
+
title: z15.string().describe("Follow-up task title"),
|
|
10344
|
+
description: z15.string().optional().describe(cardDescriptionDesc("Brief description of the follow-up work")),
|
|
10345
|
+
plan: z15.string().optional().describe("Implementation plan if known"),
|
|
10346
|
+
story_point_value: z15.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
|
|
9697
10347
|
},
|
|
9698
10348
|
async ({ title, description, plan, story_point_value }) => {
|
|
9699
10349
|
try {
|
|
@@ -9720,11 +10370,11 @@ function buildCreateSuggestionTool(connection) {
|
|
|
9720
10370
|
"create_suggestion",
|
|
9721
10371
|
"Suggest a feature, improvement, rule, or idea for the project. Duplicates are deduped and your upvote is recorded. For actionable work on this task open a follow-up task.",
|
|
9722
10372
|
{
|
|
9723
|
-
title:
|
|
9724
|
-
description:
|
|
10373
|
+
title: z15.string().describe("Short title for the suggestion"),
|
|
10374
|
+
description: z15.string().optional().describe(
|
|
9725
10375
|
"1-2 sentence description of what should change and why. Keep concise and project-focused."
|
|
9726
10376
|
),
|
|
9727
|
-
tag_names:
|
|
10377
|
+
tag_names: z15.array(z15.string()).optional().describe("Tag names to categorize the suggestion")
|
|
9728
10378
|
},
|
|
9729
10379
|
async ({ title, description, tag_names }) => {
|
|
9730
10380
|
try {
|
|
@@ -9753,8 +10403,8 @@ function buildVoteSuggestionTool(connection) {
|
|
|
9753
10403
|
"vote_suggestion",
|
|
9754
10404
|
"Vote +1 or -1 on a project suggestion. Use to express support or disagreement with a specific suggestion returned by get_suggestions.",
|
|
9755
10405
|
{
|
|
9756
|
-
suggestion_id:
|
|
9757
|
-
value:
|
|
10406
|
+
suggestion_id: z15.string().describe("The suggestion ID to vote on"),
|
|
10407
|
+
value: z15.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
|
|
9758
10408
|
},
|
|
9759
10409
|
async ({ suggestion_id, value }) => {
|
|
9760
10410
|
try {
|
|
@@ -10135,14 +10785,81 @@ function buildCommonTools(connection, config) {
|
|
|
10135
10785
|
}
|
|
10136
10786
|
|
|
10137
10787
|
// src/tools/pm-tools.ts
|
|
10138
|
-
import { z as
|
|
10788
|
+
import { z as z16 } from "zod";
|
|
10789
|
+
|
|
10790
|
+
// src/tools/task-update-tools.ts
|
|
10791
|
+
var CURRENT_TASK_ONLY = ["title", "description", "plan", "githubBranch"];
|
|
10792
|
+
function presentCurrentTaskFields(input) {
|
|
10793
|
+
return CURRENT_TASK_ONLY.filter((key) => input[key] !== void 0);
|
|
10794
|
+
}
|
|
10795
|
+
function rejectionFor(input, present) {
|
|
10796
|
+
if (input.task_id && present.length > 0) {
|
|
10797
|
+
return `Cannot update ${present.join(", ")} on another task: task_id is only valid with status. Call update_task again without task_id to change the current task, and use a separate call with task_id to move the child.`;
|
|
10798
|
+
}
|
|
10799
|
+
if (present.length === 0 && input.status === void 0) {
|
|
10800
|
+
return "No fields to update. Pass at least one of: title, description, plan, status, githubBranch.";
|
|
10801
|
+
}
|
|
10802
|
+
return null;
|
|
10803
|
+
}
|
|
10804
|
+
async function applyToCurrentTask(connection, input, applied) {
|
|
10805
|
+
if (input.plan !== void 0 || input.description !== void 0) {
|
|
10806
|
+
await connection.call("updateTaskFields", {
|
|
10807
|
+
sessionId: connection.sessionId,
|
|
10808
|
+
plan: input.plan,
|
|
10809
|
+
description: input.description
|
|
10810
|
+
});
|
|
10811
|
+
if (input.plan !== void 0) applied.push("plan");
|
|
10812
|
+
if (input.description !== void 0) applied.push("description");
|
|
10813
|
+
}
|
|
10814
|
+
if (input.title !== void 0 || input.githubBranch !== void 0) {
|
|
10815
|
+
await connection.call("updateTaskProperties", {
|
|
10816
|
+
sessionId: connection.sessionId,
|
|
10817
|
+
title: input.title,
|
|
10818
|
+
githubBranch: input.githubBranch
|
|
10819
|
+
});
|
|
10820
|
+
if (input.title !== void 0) applied.push("title");
|
|
10821
|
+
if (input.githubBranch !== void 0) applied.push("githubBranch");
|
|
10822
|
+
}
|
|
10823
|
+
if (input.status !== void 0) {
|
|
10824
|
+
await connection.call("updateTaskStatus", {
|
|
10825
|
+
sessionId: connection.sessionId,
|
|
10826
|
+
status: input.status
|
|
10827
|
+
});
|
|
10828
|
+
applied.push(`status: ${input.status}`);
|
|
10829
|
+
}
|
|
10830
|
+
}
|
|
10831
|
+
function buildTaskUpdateTool(connection) {
|
|
10832
|
+
return defineContractTool(updateTaskContract, async (input) => {
|
|
10833
|
+
const rejection = rejectionFor(input, presentCurrentTaskFields(input));
|
|
10834
|
+
if (rejection) return textResult(rejection);
|
|
10835
|
+
const applied = [];
|
|
10836
|
+
try {
|
|
10837
|
+
if (input.task_id) {
|
|
10838
|
+
await connection.call("updateChildStatus", {
|
|
10839
|
+
sessionId: connection.sessionId,
|
|
10840
|
+
childTaskId: input.task_id,
|
|
10841
|
+
status: input.status
|
|
10842
|
+
});
|
|
10843
|
+
return textResult(`Child task ${input.task_id} status updated to ${input.status}.`);
|
|
10844
|
+
}
|
|
10845
|
+
await applyToCurrentTask(connection, input, applied);
|
|
10846
|
+
return textResult(`Task updated \xB7 ${applied.join(", ")}`);
|
|
10847
|
+
} catch (error) {
|
|
10848
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
10849
|
+
const partial = applied.length > 0 ? ` Already applied: ${applied.join(", ")}.` : "";
|
|
10850
|
+
return textResult(`Failed to update task: ${detail}.${partial}`);
|
|
10851
|
+
}
|
|
10852
|
+
});
|
|
10853
|
+
}
|
|
10854
|
+
|
|
10855
|
+
// src/tools/pm-tools.ts
|
|
10139
10856
|
function buildUpdateTaskTool(connection) {
|
|
10140
10857
|
return defineTool(
|
|
10141
10858
|
"update_task_plan",
|
|
10142
10859
|
"Save the plan and/or description to the current task. In auto/building mode, save the plan BEFORE writing code and keep it current as the approach evolves \u2014 post it, then build; never pause the build waiting for approval. For children use update_subtask; for title/tags/PR use update_task_properties.",
|
|
10143
10860
|
{
|
|
10144
|
-
plan:
|
|
10145
|
-
description:
|
|
10861
|
+
plan: z16.string().optional().describe("The task plan in markdown"),
|
|
10862
|
+
description: z16.string().optional().describe(cardDescriptionDesc("Updated task description"))
|
|
10146
10863
|
},
|
|
10147
10864
|
async ({ plan, description }) => {
|
|
10148
10865
|
try {
|
|
@@ -10163,12 +10880,12 @@ function buildUpdateTaskTool(connection) {
|
|
|
10163
10880
|
function buildHandoffTool(connection) {
|
|
10164
10881
|
return defineTool(
|
|
10165
10882
|
"handoff_to_agent",
|
|
10166
|
-
"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 (
|
|
10883
|
+
"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). 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.",
|
|
10167
10884
|
{
|
|
10168
|
-
storyPoints:
|
|
10885
|
+
storyPoints: z16.number().int().positive().optional().describe(
|
|
10169
10886
|
"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."
|
|
10170
10887
|
),
|
|
10171
|
-
message:
|
|
10888
|
+
message: z16.string().optional().describe("Optional kickoff note posted to the chat alongside the handoff notice.")
|
|
10172
10889
|
},
|
|
10173
10890
|
async ({ storyPoints, message }) => {
|
|
10174
10891
|
try {
|
|
@@ -10319,7 +11036,7 @@ function buildPackTools(connection) {
|
|
|
10319
11036
|
"start_child_cloud_build",
|
|
10320
11037
|
"Start a cloud build (codespace) for a child task. Preconditions: child status is `Open`, story points set, and an agent assigned \u2014 satisfy all three with update_subtask (status/agentIdOrName/storyPointValue) first; none happen automatically. A PACK_CHILD_LIMIT error is backpressure, not failure: check list_subtasks packSlots for which children hold the in-flight slots, merge/stop one, then retry. On a feature-branch pack, dev is merged into the pack branch first so the child branches from a fresh base; a conflict is reported in the result and never blocks the launch.",
|
|
10321
11038
|
{
|
|
10322
|
-
childTaskId:
|
|
11039
|
+
childTaskId: z16.string().describe("The child task ID to start a cloud build for")
|
|
10323
11040
|
},
|
|
10324
11041
|
async ({ childTaskId }) => {
|
|
10325
11042
|
try {
|
|
@@ -10348,7 +11065,7 @@ Base sync: dev \u2192 ${sync.branch} failed \u2014 ${sync.error}. The child was
|
|
|
10348
11065
|
"stop_child_build",
|
|
10349
11066
|
"Send a graceful stop signal to a running child build's agent. Not a force-kill \u2014 the agent may take a moment to wind down. Stopping a child eventually frees its PACK_CHILD_LIMIT build slot (see list_subtasks packSlots).",
|
|
10350
11067
|
{
|
|
10351
|
-
childTaskId:
|
|
11068
|
+
childTaskId: z16.string().describe("The child task ID whose build should be stopped")
|
|
10352
11069
|
},
|
|
10353
11070
|
async ({ childTaskId }) => {
|
|
10354
11071
|
try {
|
|
@@ -10389,6 +11106,13 @@ Base sync: dev \u2192 ${sync.branch} failed \u2014 ${sync.error}. The child was
|
|
|
10389
11106
|
function buildPmTools(connection, options) {
|
|
10390
11107
|
const tools = [
|
|
10391
11108
|
buildUpdateTaskTool(connection),
|
|
11109
|
+
// The shared-skill vocabulary alongside the split legacy tools above.
|
|
11110
|
+
// Both stay REGISTERED, but the migration this comment used to defer has
|
|
11111
|
+
// now happened (A2-5/A2-6): no prompt names `update_task_plan` any more, so
|
|
11112
|
+
// it left ALWAYS_LOADED_TOOLS and is reachable via ToolSearch instead.
|
|
11113
|
+
// `update_task_properties` deliberately stayed hot — it is the only pod tool
|
|
11114
|
+
// carrying storyPointValue and risk, which ExitPlanMode requires.
|
|
11115
|
+
buildTaskUpdateTool(connection),
|
|
10392
11116
|
buildCreateSubtaskTool(connection),
|
|
10393
11117
|
buildSetTaskParentTool(connection),
|
|
10394
11118
|
buildUpdateSubtaskTool(connection),
|
|
@@ -10400,7 +11124,7 @@ function buildPmTools(connection, options) {
|
|
|
10400
11124
|
}
|
|
10401
11125
|
|
|
10402
11126
|
// src/tools/discovery-tools.ts
|
|
10403
|
-
import { z as
|
|
11127
|
+
import { z as z17 } from "zod";
|
|
10404
11128
|
var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique). The key is 'storyPointValue' \u2014 not 'storyPoints'.";
|
|
10405
11129
|
var VALID_PROPERTY_KEYS = "title, storyPointValue, tagNames, githubPRUrl, githubBranch, risk";
|
|
10406
11130
|
function describeUpdatedFields(p) {
|
|
@@ -10419,12 +11143,12 @@ function buildDiscoveryTools(connection) {
|
|
|
10419
11143
|
"update_task_properties",
|
|
10420
11144
|
"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.",
|
|
10421
11145
|
{
|
|
10422
|
-
title:
|
|
10423
|
-
storyPointValue:
|
|
10424
|
-
tagNames:
|
|
10425
|
-
githubPRUrl:
|
|
10426
|
-
githubBranch:
|
|
10427
|
-
risk:
|
|
11146
|
+
title: z17.string().optional().describe("The new task title"),
|
|
11147
|
+
storyPointValue: z17.number().optional().describe(SP_DESCRIPTION2),
|
|
11148
|
+
tagNames: z17.array(z17.string()).optional().describe("Array of tag names to assign"),
|
|
11149
|
+
githubPRUrl: z17.string().url().optional().describe("GitHub pull request URL to link to this task"),
|
|
11150
|
+
githubBranch: z17.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')"),
|
|
11151
|
+
risk: z17.enum(["critical", "high", "medium", "low"]).nullable().optional().describe(
|
|
10428
11152
|
"Risk level \u2014 how much important surface the task touches (critical/high/medium/low). Pass null to clear."
|
|
10429
11153
|
)
|
|
10430
11154
|
},
|
|
@@ -10464,7 +11188,7 @@ function buildDiscoveryTools(connection) {
|
|
|
10464
11188
|
}
|
|
10465
11189
|
|
|
10466
11190
|
// src/tools/project-tools.ts
|
|
10467
|
-
import { z as
|
|
11191
|
+
import { z as z18 } from "zod";
|
|
10468
11192
|
|
|
10469
11193
|
// src/execution/context-path-verifier.ts
|
|
10470
11194
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -10542,16 +11266,16 @@ function formatContextPathProblems(problems) {
|
|
|
10542
11266
|
}
|
|
10543
11267
|
|
|
10544
11268
|
// src/tools/project-tools.ts
|
|
10545
|
-
var CONTEXT_PATH_SHAPE =
|
|
10546
|
-
type:
|
|
11269
|
+
var CONTEXT_PATH_SHAPE = z18.object({
|
|
11270
|
+
type: z18.enum(["rule", "doc", "file", "folder"]).describe(
|
|
10547
11271
|
"Link kind \u2014 all paths are repo-relative; doc marks a synced project doc, which resolves from the workspace like rule/file"
|
|
10548
11272
|
),
|
|
10549
|
-
path:
|
|
10550
|
-
label:
|
|
10551
|
-
locator:
|
|
11273
|
+
path: z18.string().min(1).max(500).describe("Repo-relative path"),
|
|
11274
|
+
label: z18.string().max(100).optional(),
|
|
11275
|
+
locator: z18.string().min(1).max(CONTEXT_LINK_LOCATOR_MAX).regex(/^[^\r\n]*$/, "Locator cannot contain line breaks").optional().describe(
|
|
10552
11276
|
'Verified-link tether: text that must keep existing in the file. With locatorType "test" it must appear inside a real it/test/describe TITLE; with "code" anywhere in the file. Validated at write time against the checkout and re-checked by the periodic sweep \u2014 a rename/delete flags the link stale. Locators containing <> are placeholders and never checked.'
|
|
10553
11277
|
),
|
|
10554
|
-
locatorType:
|
|
11278
|
+
locatorType: z18.enum(["test", "code"]).optional().describe("How the locator must match \u2014 required iff locator is set; not valid on folder links")
|
|
10555
11279
|
}).refine((link) => link.locator === void 0 === (link.locatorType === void 0), {
|
|
10556
11280
|
message: "locator and locatorType must be provided together"
|
|
10557
11281
|
}).refine((link) => link.locator === void 0 || link.type !== "folder", {
|
|
@@ -10598,15 +11322,15 @@ function buildCreateTagTool(connection, projectId, workspaceDir) {
|
|
|
10598
11322
|
"create_tag",
|
|
10599
11323
|
"Create a project tag. Include a crisp description (\u2264255 chars \u2014 the summary) and contextPaths (rule/doc/file/folder links agents auto-load when working on matching tasks); put the full spec in overview. Set parentTagIds to place the tag in the hierarchy right away. Every contextPath is checked against the repo checkout \u2014 a path that does not exist, or whose type does not match what is on disk, rejects the whole call. Fails if the name already exists.",
|
|
10600
11324
|
{
|
|
10601
|
-
name:
|
|
10602
|
-
color:
|
|
10603
|
-
description:
|
|
10604
|
-
overview:
|
|
10605
|
-
overviewPath:
|
|
11325
|
+
name: z18.string().min(1).max(50),
|
|
11326
|
+
color: z18.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("#RRGGBB (default gray)"),
|
|
11327
|
+
description: z18.string().max(TAG_DESCRIPTION_MAX).optional(),
|
|
11328
|
+
overview: z18.string().max(TAG_OVERVIEW_MAX).optional().describe("Full markdown glossary body \u2014 the term's spec"),
|
|
11329
|
+
overviewPath: z18.string().min(1).max(500).optional().describe(
|
|
10606
11330
|
"Repo file to source the overview from (base-branch content is served everywhere). A not-yet-merged path is fine \u2014 the tag serves the stored overview as fallback until the file lands."
|
|
10607
11331
|
),
|
|
10608
|
-
parentTagIds:
|
|
10609
|
-
contextPaths:
|
|
11332
|
+
parentTagIds: z18.array(z18.string()).max(25).optional().describe("Parent tag ids from list_tags (multi-parent DAG) to link at create time"),
|
|
11333
|
+
contextPaths: z18.array(CONTEXT_PATH_SHAPE).max(20).optional()
|
|
10610
11334
|
},
|
|
10611
11335
|
async ({ name, color, description, overview, overviewPath, parentTagIds, contextPaths }) => {
|
|
10612
11336
|
const rejection = await rejectBadContextPaths(contextPaths, workspaceDir);
|
|
@@ -10634,19 +11358,19 @@ function buildUpdateTagTool(connection, projectId, taskId, workspaceDir) {
|
|
|
10634
11358
|
"update_tag",
|
|
10635
11359
|
"Update a tag's name, color, description (\u2264255), markdown overview, parent tags, or contextPaths. contextPaths and parentTagIds are FULL replacements \u2014 include what you want to keep. ALWAYS pass a short reason; it lands in the tag's revision history (with your current card auto-stamped) so the team sees why the glossary changed. Every contextPath is checked against the repo checkout \u2014 a path that does not exist, or whose type does not match what is on disk, rejects the whole call and nothing is written.",
|
|
10636
11360
|
{
|
|
10637
|
-
tagId:
|
|
10638
|
-
name:
|
|
10639
|
-
color:
|
|
10640
|
-
description:
|
|
10641
|
-
overview:
|
|
11361
|
+
tagId: z18.string().describe("Tag id from list_tags"),
|
|
11362
|
+
name: z18.string().min(1).max(50).optional(),
|
|
11363
|
+
color: z18.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
|
11364
|
+
description: z18.string().max(TAG_DESCRIPTION_MAX).optional(),
|
|
11365
|
+
overview: z18.string().max(TAG_OVERVIEW_MAX).nullable().optional().describe(
|
|
10642
11366
|
"Full markdown glossary body; null clears it. REJECTED while overviewPath is set \u2014 edit the sourced file in the repo instead"
|
|
10643
11367
|
),
|
|
10644
|
-
overviewPath:
|
|
11368
|
+
overviewPath: z18.string().min(1).max(500).nullable().optional().describe(
|
|
10645
11369
|
"Repo file to source the overview from (null clears back to the stored overview). A not-yet-merged path is fine \u2014 the stored overview serves as fallback until the file lands on the base branch."
|
|
10646
11370
|
),
|
|
10647
|
-
parentTagIds:
|
|
10648
|
-
reason:
|
|
10649
|
-
contextPaths:
|
|
11371
|
+
parentTagIds: z18.array(z18.string()).max(25).optional().describe("Full-set replacement of the tag's parent tags (multi-parent DAG)"),
|
|
11372
|
+
reason: z18.string().max(TAG_REASON_MAX).optional().describe("One line on why \u2014 shown in the tag's revision history"),
|
|
11373
|
+
contextPaths: z18.array(CONTEXT_PATH_SHAPE).max(20).optional()
|
|
10650
11374
|
},
|
|
10651
11375
|
async ({
|
|
10652
11376
|
tagId,
|
|
@@ -10704,8 +11428,8 @@ function buildPostToProjectChatTool(connection, projectId) {
|
|
|
10704
11428
|
"post_to_project_chat",
|
|
10705
11429
|
"Post a markdown message to the PROJECT chat \u2014 use once at the end of an audit for the summary the team reads.",
|
|
10706
11430
|
{
|
|
10707
|
-
message:
|
|
10708
|
-
kind:
|
|
11431
|
+
message: z18.string().min(1).max(2e4),
|
|
11432
|
+
kind: z18.enum(["tag_audit_summary"]).optional().describe(
|
|
10709
11433
|
"Set to 'tag_audit_summary' when posting a tag-audit summary so it is also saved to the persistent tag history"
|
|
10710
11434
|
)
|
|
10711
11435
|
},
|
|
@@ -10724,7 +11448,7 @@ function buildGetProjectTaskTool(connection, projectId) {
|
|
|
10724
11448
|
"get_project_task",
|
|
10725
11449
|
"Fetch any task in the project by id or slug: title, description, plan, status, and metadata. The audit evidence trail starts here.",
|
|
10726
11450
|
{
|
|
10727
|
-
taskId:
|
|
11451
|
+
taskId: z18.string().describe("Task id or slug")
|
|
10728
11452
|
},
|
|
10729
11453
|
async ({ taskId }) => {
|
|
10730
11454
|
try {
|
|
@@ -10742,8 +11466,8 @@ function buildReadProjectTaskChatTool(connection, projectId) {
|
|
|
10742
11466
|
"read_project_task_chat",
|
|
10743
11467
|
"Read any project task's chat messages (newest last). role 'user' rows are HUMAN turns; 'assistant'/'system' rows are agent posts and activity-log entries.",
|
|
10744
11468
|
{
|
|
10745
|
-
taskId:
|
|
10746
|
-
limit:
|
|
11469
|
+
taskId: z18.string().describe("Task id or slug"),
|
|
11470
|
+
limit: z18.number().int().min(1).max(200).optional().describe("Messages to fetch (default 50)")
|
|
10747
11471
|
},
|
|
10748
11472
|
async ({ taskId, limit }) => {
|
|
10749
11473
|
try {
|
|
@@ -10765,9 +11489,9 @@ function buildGetProjectTaskLogsTool(connection, projectId) {
|
|
|
10765
11489
|
"get_project_task_logs",
|
|
10766
11490
|
"Read any project task's persisted agent event stream (message / tool_use / turn_end / error / completed). Turn boundaries are turn_end events. Entries are truncated to ~2KB each; max 500 per call.",
|
|
10767
11491
|
{
|
|
10768
|
-
taskId:
|
|
10769
|
-
limit:
|
|
10770
|
-
source:
|
|
11492
|
+
taskId: z18.string().describe("Task id or slug"),
|
|
11493
|
+
limit: z18.number().int().min(1).max(500).optional().describe("Entries to fetch (default 50)"),
|
|
11494
|
+
source: z18.enum(["agent", "application"]).optional().describe("Filter: 'agent' = model events (default useful for grading)")
|
|
10771
11495
|
},
|
|
10772
11496
|
async ({ taskId, limit, source }) => {
|
|
10773
11497
|
try {
|
|
@@ -10785,44 +11509,44 @@ function buildGetProjectTaskLogsTool(connection, projectId) {
|
|
|
10785
11509
|
{ annotations: { readOnlyHint: true } }
|
|
10786
11510
|
);
|
|
10787
11511
|
}
|
|
10788
|
-
var TURN_GRADE_SHAPE =
|
|
10789
|
-
turnIndex:
|
|
10790
|
-
phase:
|
|
10791
|
-
grade:
|
|
10792
|
-
reasoning:
|
|
10793
|
-
eventType:
|
|
10794
|
-
eventSummary:
|
|
11512
|
+
var TURN_GRADE_SHAPE = z18.object({
|
|
11513
|
+
turnIndex: z18.number().int().min(0),
|
|
11514
|
+
phase: z18.enum(["planning", "building", "human"]),
|
|
11515
|
+
grade: z18.enum(["correct", "neutral", "blunder"]),
|
|
11516
|
+
reasoning: z18.string(),
|
|
11517
|
+
eventType: z18.string().describe('e.g. "message", "tool_use", "human_message"'),
|
|
11518
|
+
eventSummary: z18.string().max(200).describe("\u2264120 chars of what happened this turn")
|
|
10795
11519
|
});
|
|
10796
|
-
var HUMAN_EVAL_SHAPE =
|
|
10797
|
-
messageIndex:
|
|
10798
|
-
rating:
|
|
10799
|
-
reasoning:
|
|
11520
|
+
var HUMAN_EVAL_SHAPE = z18.object({
|
|
11521
|
+
messageIndex: z18.number().int().min(0).describe("Index into the task's human messages, oldest first"),
|
|
11522
|
+
rating: z18.number().int().min(-1).max(1),
|
|
11523
|
+
reasoning: z18.string()
|
|
10800
11524
|
});
|
|
10801
11525
|
function buildReportTaskAuditResultTool(connection, projectId) {
|
|
10802
11526
|
return defineTool(
|
|
10803
11527
|
"report_task_audit_result",
|
|
10804
11528
|
"Persist one audited task's grades (call once per task after grading it). Pass error instead to mark the audit failed when the evidence is unusable.",
|
|
10805
11529
|
{
|
|
10806
|
-
taskId:
|
|
10807
|
-
summary:
|
|
10808
|
-
turnGrades:
|
|
10809
|
-
planningAccuracy:
|
|
10810
|
-
buildingAccuracy:
|
|
10811
|
-
humanAccuracy:
|
|
10812
|
-
planningCorrect:
|
|
10813
|
-
planningNeutral:
|
|
10814
|
-
planningBlunder:
|
|
10815
|
-
buildingCorrect:
|
|
10816
|
-
buildingNeutral:
|
|
10817
|
-
buildingBlunder:
|
|
10818
|
-
humanCorrect:
|
|
10819
|
-
humanNeutral:
|
|
10820
|
-
humanBlunder:
|
|
10821
|
-
humanEvaluations:
|
|
10822
|
-
suggestionIds:
|
|
10823
|
-
auditCostUsd:
|
|
10824
|
-
model:
|
|
10825
|
-
error:
|
|
11530
|
+
taskId: z18.string().describe("The audited task's id (NOT slug)"),
|
|
11531
|
+
summary: z18.string().describe("3-6 sentences: what went well, what was wasted"),
|
|
11532
|
+
turnGrades: z18.array(TURN_GRADE_SHAPE),
|
|
11533
|
+
planningAccuracy: z18.number().min(0).max(1).nullable(),
|
|
11534
|
+
buildingAccuracy: z18.number().min(0).max(1).nullable(),
|
|
11535
|
+
humanAccuracy: z18.number().min(0).max(1).nullable(),
|
|
11536
|
+
planningCorrect: z18.number().int().min(0),
|
|
11537
|
+
planningNeutral: z18.number().int().min(0),
|
|
11538
|
+
planningBlunder: z18.number().int().min(0),
|
|
11539
|
+
buildingCorrect: z18.number().int().min(0),
|
|
11540
|
+
buildingNeutral: z18.number().int().min(0),
|
|
11541
|
+
buildingBlunder: z18.number().int().min(0),
|
|
11542
|
+
humanCorrect: z18.number().int().min(0),
|
|
11543
|
+
humanNeutral: z18.number().int().min(0),
|
|
11544
|
+
humanBlunder: z18.number().int().min(0),
|
|
11545
|
+
humanEvaluations: z18.array(HUMAN_EVAL_SHAPE).optional(),
|
|
11546
|
+
suggestionIds: z18.array(z18.string()).describe("Suggestion ids filed for this task, or []"),
|
|
11547
|
+
auditCostUsd: z18.number().nullable(),
|
|
11548
|
+
model: z18.string().nullable().describe("The model you are running as"),
|
|
11549
|
+
error: z18.string().optional().describe("Set ONLY to mark this task's audit failed")
|
|
10826
11550
|
},
|
|
10827
11551
|
async (input) => {
|
|
10828
11552
|
try {
|
|
@@ -10879,21 +11603,13 @@ function buildProjectTools(connection, projectId, workspaceDir) {
|
|
|
10879
11603
|
}
|
|
10880
11604
|
|
|
10881
11605
|
// src/tools/drive-tools.ts
|
|
10882
|
-
import { z as z17 } from "zod";
|
|
10883
|
-
var MAX_CONTENT_CHARS = 1e6;
|
|
10884
11606
|
var MAX_READ_CHARS = 1e5;
|
|
10885
11607
|
function errText2(prefix, error) {
|
|
10886
11608
|
return textResult(`${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
10887
11609
|
}
|
|
10888
11610
|
function buildDriveListFilesTool(connection, projectId) {
|
|
10889
|
-
return
|
|
10890
|
-
|
|
10891
|
-
"List files and folders in the project's connected Google Drive folder. Omit folderId to list the project's root folder. Returns id, name, mimeType, size, and modified time for each entry.",
|
|
10892
|
-
{
|
|
10893
|
-
folderId: z17.string().optional().describe("Folder to list. Defaults to the project's connected root folder."),
|
|
10894
|
-
search: z17.string().max(200).optional().describe("Only return names containing this text"),
|
|
10895
|
-
limit: z17.number().int().min(1).max(200).optional().describe("Max entries (default 100)")
|
|
10896
|
-
},
|
|
11611
|
+
return defineContractTool(
|
|
11612
|
+
driveListFilesContract,
|
|
10897
11613
|
async ({ folderId, search, limit }) => {
|
|
10898
11614
|
try {
|
|
10899
11615
|
const result = await connection.call("listProjectDriveFiles", {
|
|
@@ -10911,10 +11627,8 @@ function buildDriveListFilesTool(connection, projectId) {
|
|
|
10911
11627
|
);
|
|
10912
11628
|
}
|
|
10913
11629
|
function buildDriveReadFileTool(connection, projectId) {
|
|
10914
|
-
return
|
|
10915
|
-
|
|
10916
|
-
"Read a file's text content from the project's connected Google Drive folder. Google Docs, Sheets, and Slides are exported to text automatically. Content over 100 KB is truncated.",
|
|
10917
|
-
{ fileId: z17.string().describe("Drive file id, as returned by drive_list_files") },
|
|
11630
|
+
return defineContractTool(
|
|
11631
|
+
driveReadFileContract,
|
|
10918
11632
|
async ({ fileId }) => {
|
|
10919
11633
|
try {
|
|
10920
11634
|
const result = await connection.call("readProjectDriveFile", { projectId, fileId });
|
|
@@ -10936,15 +11650,8 @@ ${content}`);
|
|
|
10936
11650
|
);
|
|
10937
11651
|
}
|
|
10938
11652
|
function buildDriveCreateFileTool(connection, projectId) {
|
|
10939
|
-
return
|
|
10940
|
-
|
|
10941
|
-
"Create a new file in the project's connected Google Drive folder. Use drive_update_file to change an existing file instead.",
|
|
10942
|
-
{
|
|
10943
|
-
name: z17.string().min(1).max(255).describe("File name, without any path separators"),
|
|
10944
|
-
content: z17.string().max(MAX_CONTENT_CHARS).describe("File content, UTF-8 text"),
|
|
10945
|
-
mimeType: z17.string().optional().describe("MIME type (default text/plain)"),
|
|
10946
|
-
folderId: z17.string().optional().describe("Destination folder. Defaults to the project's connected root folder.")
|
|
10947
|
-
},
|
|
11653
|
+
return defineContractTool(
|
|
11654
|
+
driveCreateFileContract,
|
|
10948
11655
|
async ({ name, content, mimeType, folderId }) => {
|
|
10949
11656
|
try {
|
|
10950
11657
|
const file = await connection.call("createProjectDriveFile", {
|
|
@@ -10962,14 +11669,8 @@ function buildDriveCreateFileTool(connection, projectId) {
|
|
|
10962
11669
|
);
|
|
10963
11670
|
}
|
|
10964
11671
|
function buildDriveUpdateFileTool(connection, projectId) {
|
|
10965
|
-
return
|
|
10966
|
-
|
|
10967
|
-
"Replace the content of an existing file in the project's connected Google Drive folder. This overwrites the whole file. Google-native documents cannot be overwritten.",
|
|
10968
|
-
{
|
|
10969
|
-
fileId: z17.string().describe("Drive file id, as returned by drive_list_files"),
|
|
10970
|
-
content: z17.string().max(MAX_CONTENT_CHARS).describe("Replacement content, UTF-8 text"),
|
|
10971
|
-
mimeType: z17.string().optional().describe("MIME type (defaults to the file's current type)")
|
|
10972
|
-
},
|
|
11672
|
+
return defineContractTool(
|
|
11673
|
+
driveUpdateFileContract,
|
|
10973
11674
|
async ({ fileId, content, mimeType }) => {
|
|
10974
11675
|
try {
|
|
10975
11676
|
const file = await connection.call("updateProjectDriveFile", {
|
|
@@ -10986,10 +11687,8 @@ function buildDriveUpdateFileTool(connection, projectId) {
|
|
|
10986
11687
|
);
|
|
10987
11688
|
}
|
|
10988
11689
|
function buildDriveDeleteFileTool(connection, projectId) {
|
|
10989
|
-
return
|
|
10990
|
-
|
|
10991
|
-
"Move a file in the project's connected Google Drive folder to the Drive trash. The file is recoverable from the trash; it is never permanently deleted.",
|
|
10992
|
-
{ fileId: z17.string().describe("Drive file id, as returned by drive_list_files") },
|
|
11690
|
+
return defineContractTool(
|
|
11691
|
+
driveDeleteFileContract,
|
|
10993
11692
|
async ({ fileId }) => {
|
|
10994
11693
|
try {
|
|
10995
11694
|
const result = await connection.call("deleteProjectDriveFile", { projectId, fileId });
|
|
@@ -11001,13 +11700,8 @@ function buildDriveDeleteFileTool(connection, projectId) {
|
|
|
11001
11700
|
);
|
|
11002
11701
|
}
|
|
11003
11702
|
function buildDriveCreateFolderTool(connection, projectId) {
|
|
11004
|
-
return
|
|
11005
|
-
|
|
11006
|
-
"Create a folder inside the project's connected Google Drive folder.",
|
|
11007
|
-
{
|
|
11008
|
-
name: z17.string().min(1).max(255).describe("Folder name, without any path separators"),
|
|
11009
|
-
folderId: z17.string().optional().describe("Parent folder. Defaults to the project's connected root folder.")
|
|
11010
|
-
},
|
|
11703
|
+
return defineContractTool(
|
|
11704
|
+
driveCreateFolderContract,
|
|
11011
11705
|
async ({ name, folderId }) => {
|
|
11012
11706
|
try {
|
|
11013
11707
|
const folder = await connection.call("createProjectDriveFolder", {
|
|
@@ -11033,10 +11727,209 @@ function buildDriveTools(connection, projectId) {
|
|
|
11033
11727
|
];
|
|
11034
11728
|
}
|
|
11035
11729
|
|
|
11730
|
+
// src/tools/integration-tools.ts
|
|
11731
|
+
function errText3(prefix, error) {
|
|
11732
|
+
return textResult(`${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
11733
|
+
}
|
|
11734
|
+
function buildListProjectIntegrationsTool(connection, projectId) {
|
|
11735
|
+
return defineContractTool(listProjectIntegrationsContract, async () => {
|
|
11736
|
+
try {
|
|
11737
|
+
const res = await connection.call("listProjectIntegrations", { projectId });
|
|
11738
|
+
return textResult(JSON.stringify(res, null, 2));
|
|
11739
|
+
} catch (error) {
|
|
11740
|
+
return errText3("Failed to list integrations", error);
|
|
11741
|
+
}
|
|
11742
|
+
});
|
|
11743
|
+
}
|
|
11744
|
+
function buildListProjectChannelsTool(connection, projectId) {
|
|
11745
|
+
return defineContractTool(listProjectChannelsContract, async () => {
|
|
11746
|
+
try {
|
|
11747
|
+
const channels = await connection.call("listProjectChannels", { projectId });
|
|
11748
|
+
if (channels.length === 0) {
|
|
11749
|
+
return textResult(
|
|
11750
|
+
"No work channels are registered for this project. An admin registers them in project settings; until then no channel is readable, whatever the bot can see in the workspace."
|
|
11751
|
+
);
|
|
11752
|
+
}
|
|
11753
|
+
return textResult(JSON.stringify(channels, null, 2));
|
|
11754
|
+
} catch (error) {
|
|
11755
|
+
return errText3("Failed to list work channels", error);
|
|
11756
|
+
}
|
|
11757
|
+
});
|
|
11758
|
+
}
|
|
11759
|
+
function buildReadChannelMessagesTool(connection, projectId) {
|
|
11760
|
+
return defineContractTool(readChannelMessagesContract, async (input) => {
|
|
11761
|
+
try {
|
|
11762
|
+
const res = await connection.call("readChannelMessages", {
|
|
11763
|
+
projectId,
|
|
11764
|
+
channelId: input.channelId,
|
|
11765
|
+
limit: input.limit,
|
|
11766
|
+
before: input.before,
|
|
11767
|
+
after: input.after,
|
|
11768
|
+
threadTs: input.threadTs
|
|
11769
|
+
});
|
|
11770
|
+
return textResult(JSON.stringify(res, null, 2));
|
|
11771
|
+
} catch (error) {
|
|
11772
|
+
return errText3("Failed to read channel messages", error);
|
|
11773
|
+
}
|
|
11774
|
+
});
|
|
11775
|
+
}
|
|
11776
|
+
function buildPostChannelMessageTool(connection, projectId) {
|
|
11777
|
+
return defineContractTool(postChannelMessageContract, async (input) => {
|
|
11778
|
+
try {
|
|
11779
|
+
const res = await connection.call("postChannelMessage", {
|
|
11780
|
+
projectId,
|
|
11781
|
+
channelId: input.channelId,
|
|
11782
|
+
text: input.text,
|
|
11783
|
+
threadTs: input.threadTs
|
|
11784
|
+
});
|
|
11785
|
+
return textResult(`Posted to ${res.channelId} (message ${res.messageId}).`);
|
|
11786
|
+
} catch (error) {
|
|
11787
|
+
return errText3("Failed to post to channel", error);
|
|
11788
|
+
}
|
|
11789
|
+
});
|
|
11790
|
+
}
|
|
11791
|
+
function buildAnalyticsSummaryTool(connection, projectId) {
|
|
11792
|
+
return defineContractTool(getAnalyticsSummaryContract, async (input) => {
|
|
11793
|
+
try {
|
|
11794
|
+
const res = await connection.call("getProjectAnalyticsSummary", {
|
|
11795
|
+
projectId,
|
|
11796
|
+
rangeDays: input.rangeDays,
|
|
11797
|
+
campaign: input.campaign
|
|
11798
|
+
});
|
|
11799
|
+
if (!res.configured) {
|
|
11800
|
+
return textResult(res.message ?? "Google Analytics is not configured for this project.");
|
|
11801
|
+
}
|
|
11802
|
+
return textResult(JSON.stringify(res, null, 2));
|
|
11803
|
+
} catch (error) {
|
|
11804
|
+
return errText3("Failed to read the analytics summary", error);
|
|
11805
|
+
}
|
|
11806
|
+
});
|
|
11807
|
+
}
|
|
11808
|
+
function buildChannelTools(connection, projectId) {
|
|
11809
|
+
return [
|
|
11810
|
+
buildListProjectChannelsTool(connection, projectId),
|
|
11811
|
+
buildReadChannelMessagesTool(connection, projectId),
|
|
11812
|
+
buildPostChannelMessageTool(connection, projectId)
|
|
11813
|
+
];
|
|
11814
|
+
}
|
|
11815
|
+
|
|
11816
|
+
// src/tools/log-tools.ts
|
|
11817
|
+
function portsFor(connection, projectId) {
|
|
11818
|
+
return {
|
|
11819
|
+
queryGcpLogs: (params) => connection.call("queryProjectGcpLogs", { ...params, projectId }),
|
|
11820
|
+
queryGrafanaLogs: (params) => connection.call("queryProjectGrafanaLogs", { ...params, projectId })
|
|
11821
|
+
};
|
|
11822
|
+
}
|
|
11823
|
+
function buildQueryGcpLogsTool(connection, projectId) {
|
|
11824
|
+
return defineContractTool(queryGcpLogsContract, async (input) => {
|
|
11825
|
+
try {
|
|
11826
|
+
return textResult(await runQueryGcpLogs(portsFor(connection, projectId), input));
|
|
11827
|
+
} catch (error) {
|
|
11828
|
+
return textResult(
|
|
11829
|
+
`Failed to query GCP logs: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
11830
|
+
);
|
|
11831
|
+
}
|
|
11832
|
+
});
|
|
11833
|
+
}
|
|
11834
|
+
function buildQueryGrafanaLogsTool(connection, projectId) {
|
|
11835
|
+
return defineContractTool(queryGrafanaLogsContract, async (input) => {
|
|
11836
|
+
try {
|
|
11837
|
+
return textResult(await runQueryGrafanaLogs(portsFor(connection, projectId), input));
|
|
11838
|
+
} catch (error) {
|
|
11839
|
+
return textResult(
|
|
11840
|
+
`Failed to query Grafana logs: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
11841
|
+
);
|
|
11842
|
+
}
|
|
11843
|
+
});
|
|
11844
|
+
}
|
|
11845
|
+
|
|
11846
|
+
// src/tools/meeting-tools.ts
|
|
11847
|
+
function errText4(prefix, error) {
|
|
11848
|
+
return textResult(`${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
11849
|
+
}
|
|
11850
|
+
function buildMeetingTools(connection, projectId) {
|
|
11851
|
+
return [
|
|
11852
|
+
defineContractTool(
|
|
11853
|
+
listMeetingsContract,
|
|
11854
|
+
async (input) => {
|
|
11855
|
+
try {
|
|
11856
|
+
const res = await connection.call("listMeetings", {
|
|
11857
|
+
projectId,
|
|
11858
|
+
limit: input.limit,
|
|
11859
|
+
search: input.search
|
|
11860
|
+
});
|
|
11861
|
+
if (res.meetings.length === 0) {
|
|
11862
|
+
return textResult(
|
|
11863
|
+
input.search ? `No meetings match "${input.search}". Try list_meetings with no search to see what exists.` : "This project has no meetings yet. They are added by pasting a transcript in the Meetings tab, or from Slack."
|
|
11864
|
+
);
|
|
11865
|
+
}
|
|
11866
|
+
return textResult(JSON.stringify(res.meetings, null, 2));
|
|
11867
|
+
} catch (error) {
|
|
11868
|
+
return errText4("Failed to list meetings", error);
|
|
11869
|
+
}
|
|
11870
|
+
},
|
|
11871
|
+
{ annotations: { readOnlyHint: true } }
|
|
11872
|
+
),
|
|
11873
|
+
defineContractTool(
|
|
11874
|
+
getMeetingContract,
|
|
11875
|
+
async (input) => {
|
|
11876
|
+
try {
|
|
11877
|
+
const res = await connection.call("getMeeting", {
|
|
11878
|
+
projectId,
|
|
11879
|
+
meetingId: input.meetingId
|
|
11880
|
+
});
|
|
11881
|
+
return textResult(JSON.stringify(res, null, 2));
|
|
11882
|
+
} catch (error) {
|
|
11883
|
+
return errText4("Failed to read the meeting", error);
|
|
11884
|
+
}
|
|
11885
|
+
},
|
|
11886
|
+
{ annotations: { readOnlyHint: true } }
|
|
11887
|
+
),
|
|
11888
|
+
defineContractTool(
|
|
11889
|
+
readMeetingTranscriptContract,
|
|
11890
|
+
async (input) => {
|
|
11891
|
+
try {
|
|
11892
|
+
const res = await connection.call("readMeetingTranscript", {
|
|
11893
|
+
projectId,
|
|
11894
|
+
meetingId: input.meetingId,
|
|
11895
|
+
offset: input.offset,
|
|
11896
|
+
limit: input.limit
|
|
11897
|
+
});
|
|
11898
|
+
const header = `${res.title} \u2014 segments ${res.offset + 1}-${res.offset + res.lines.length} of ${res.segmentCount}`;
|
|
11899
|
+
const footer = res.nextOffset === void 0 ? "" : `
|
|
11900
|
+
|
|
11901
|
+
-- more remains: pass offset=${res.nextOffset} to continue`;
|
|
11902
|
+
return textResult(`${header}
|
|
11903
|
+
|
|
11904
|
+
${res.lines.join("\n")}${footer}`);
|
|
11905
|
+
} catch (error) {
|
|
11906
|
+
return errText4("Failed to read the transcript", error);
|
|
11907
|
+
}
|
|
11908
|
+
},
|
|
11909
|
+
{ annotations: { readOnlyHint: true } }
|
|
11910
|
+
)
|
|
11911
|
+
];
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11914
|
+
// src/tools/connected-tools.ts
|
|
11915
|
+
function connectedToolsFor(connection, context) {
|
|
11916
|
+
const projectId = context?.projectId;
|
|
11917
|
+
if (!projectId) return [];
|
|
11918
|
+
return [
|
|
11919
|
+
...context.googleDriveConnected ? buildDriveTools(connection, projectId) : [],
|
|
11920
|
+
buildListProjectIntegrationsTool(connection, projectId),
|
|
11921
|
+
...context.chatChannelsConfigured ? buildChannelTools(connection, projectId) : [],
|
|
11922
|
+
...context.meetingsAvailable ? buildMeetingTools(connection, projectId) : [],
|
|
11923
|
+
...context.googleAnalyticsConfigured ? [buildAnalyticsSummaryTool(connection, projectId)] : [],
|
|
11924
|
+
...context.gcpLogsConfigured ? [buildQueryGcpLogsTool(connection, projectId)] : [],
|
|
11925
|
+
...context.grafanaLogsConfigured ? [buildQueryGrafanaLogsTool(connection, projectId)] : []
|
|
11926
|
+
];
|
|
11927
|
+
}
|
|
11928
|
+
|
|
11036
11929
|
// src/tools/code-review-tools.ts
|
|
11037
11930
|
import { execFile } from "child_process";
|
|
11038
11931
|
import { promisify } from "util";
|
|
11039
|
-
import { z as
|
|
11932
|
+
import { z as z19 } from "zod";
|
|
11040
11933
|
async function endReviewSession(connection, reason) {
|
|
11041
11934
|
await connection.call("endReviewSession", {
|
|
11042
11935
|
sessionId: connection.sessionId,
|
|
@@ -11044,26 +11937,26 @@ async function endReviewSession(connection, reason) {
|
|
|
11044
11937
|
});
|
|
11045
11938
|
}
|
|
11046
11939
|
var RISK_LEVELS2 = ["critical", "high", "medium", "low"];
|
|
11047
|
-
var reviewedShaSchema =
|
|
11940
|
+
var reviewedShaSchema = z19.string().regex(/^[0-9a-f]{40}$/i).describe("REQUIRED. The full 40-character commit SHA this verdict reviews.");
|
|
11048
11941
|
var riskDescription = "REQUIRED. The risk level this change carries, judged by the surface area it touches: critical = touches critical/foundational surface, high = important surface, medium = moderate, low = small/isolated. Set this on every verdict. You have authority to override a risk level already set on the task if you disagree with it.";
|
|
11049
|
-
var ReviewGuideToolSchema =
|
|
11050
|
-
reviewedSha:
|
|
11942
|
+
var ReviewGuideToolSchema = z19.strictObject({
|
|
11943
|
+
reviewedSha: z19.string().regex(/^[0-9a-f]{40}$/i).describe(
|
|
11051
11944
|
"REQUIRED. The PR's current head as a full 40-char SHA. Run `git rev-parse HEAD` immediately before this call \u2014 never extend an abbreviated hash into 40 characters."
|
|
11052
11945
|
),
|
|
11053
|
-
overview:
|
|
11054
|
-
sections:
|
|
11055
|
-
|
|
11056
|
-
title:
|
|
11057
|
-
explanation:
|
|
11058
|
-
classification:
|
|
11059
|
-
files:
|
|
11060
|
-
|
|
11061
|
-
path:
|
|
11946
|
+
overview: z19.string().min(1).max(6e4).describe("REQUIRED. Plain-text walkthrough intro, max 3000 characters. Keep it short."),
|
|
11947
|
+
sections: z19.array(
|
|
11948
|
+
z19.strictObject({
|
|
11949
|
+
title: z19.string().min(1).max(160),
|
|
11950
|
+
explanation: z19.string().min(1).max(2e3),
|
|
11951
|
+
classification: z19.enum(["core", "supporting"]).optional(),
|
|
11952
|
+
files: z19.array(
|
|
11953
|
+
z19.strictObject({
|
|
11954
|
+
path: z19.string().min(1).max(500).describe(
|
|
11062
11955
|
"A file the PR's diff actually changed. Context files you merely read are rejected."
|
|
11063
11956
|
),
|
|
11064
|
-
startLine:
|
|
11065
|
-
endLine:
|
|
11066
|
-
hunkHeader:
|
|
11957
|
+
startLine: z19.number().int().positive().max(1e6).optional(),
|
|
11958
|
+
endLine: z19.number().int().positive().max(1e6).optional(),
|
|
11959
|
+
hunkHeader: z19.string().min(1).max(300).optional().describe(
|
|
11067
11960
|
"Optional anchor, matched byte-exactly against the full hunk header line from `git diff` INCLUDING the context text after the second @@. Copy it verbatim from `git diff <base>..HEAD -- <file> | grep '^@@'`, or omit anchors entirely (path-only entries always validate)."
|
|
11068
11961
|
)
|
|
11069
11962
|
})
|
|
@@ -11147,8 +12040,8 @@ function buildApproveCodeReviewTool(connection) {
|
|
|
11147
12040
|
"Approve the code review and exit. Use when the diff passes all review criteria. Requires a summary and a risk level \u2014 for changes, use request_code_changes with a structured issues[] list.",
|
|
11148
12041
|
{
|
|
11149
12042
|
reviewedSha: reviewedShaSchema,
|
|
11150
|
-
summary:
|
|
11151
|
-
risk:
|
|
12043
|
+
summary: z19.string().describe("Brief summary of what was reviewed and why it looks good"),
|
|
12044
|
+
risk: z19.enum(RISK_LEVELS2).describe(riskDescription)
|
|
11152
12045
|
},
|
|
11153
12046
|
async ({ reviewedSha, summary, risk }) => {
|
|
11154
12047
|
const content = `**Code Review: Approved** :white_check_mark:
|
|
@@ -11179,16 +12072,16 @@ function buildRequestCodeChangesTool(connection) {
|
|
|
11179
12072
|
"Request changes during code review and exit. Use when substantive issues must be fixed before merge. Each issue: { file, line?, severity: critical|major|minor, description }.",
|
|
11180
12073
|
{
|
|
11181
12074
|
reviewedSha: reviewedShaSchema,
|
|
11182
|
-
issues:
|
|
11183
|
-
|
|
11184
|
-
file:
|
|
11185
|
-
line:
|
|
11186
|
-
severity:
|
|
11187
|
-
description:
|
|
12075
|
+
issues: z19.array(
|
|
12076
|
+
z19.object({
|
|
12077
|
+
file: z19.string().describe("File path where the issue was found"),
|
|
12078
|
+
line: z19.number().optional().describe("Line number (if applicable)"),
|
|
12079
|
+
severity: z19.enum(["critical", "major", "minor"]).describe("Issue severity"),
|
|
12080
|
+
description: z19.string().describe("What is wrong and how to fix it")
|
|
11188
12081
|
})
|
|
11189
12082
|
).describe("List of issues found during review"),
|
|
11190
|
-
summary:
|
|
11191
|
-
risk:
|
|
12083
|
+
summary: z19.string().describe("Brief overall summary of the review findings"),
|
|
12084
|
+
risk: z19.enum(RISK_LEVELS2).describe(riskDescription)
|
|
11192
12085
|
},
|
|
11193
12086
|
async ({ reviewedSha, issues, summary, risk }) => {
|
|
11194
12087
|
const issueLines = issues.map((issue) => {
|
|
@@ -11237,26 +12130,30 @@ function getTaskModeTools(agentMode, connection) {
|
|
|
11237
12130
|
}
|
|
11238
12131
|
function getModeTools(agentMode, connection, config, context) {
|
|
11239
12132
|
if (config.mode === "pack") {
|
|
11240
|
-
return buildPmTools(connection, {
|
|
12133
|
+
return buildPmTools(connection, {
|
|
12134
|
+
includePackTools: config.packExecution === "fan-out"
|
|
12135
|
+
});
|
|
11241
12136
|
}
|
|
11242
12137
|
if (config.mode === "task") return getTaskModeTools(agentMode, connection);
|
|
12138
|
+
const packToolsFor = (isParent) => ({
|
|
12139
|
+
includePackTools: !!isParent && config.packExecution === "fan-out"
|
|
12140
|
+
});
|
|
11243
12141
|
switch (agentMode) {
|
|
11244
12142
|
case "building":
|
|
11245
|
-
return context?.isParentTask ? buildPmTools(connection,
|
|
12143
|
+
return context?.isParentTask ? buildPmTools(connection, packToolsFor(true)) : [];
|
|
11246
12144
|
case "review":
|
|
11247
12145
|
case "auto":
|
|
11248
12146
|
case "discovery":
|
|
11249
12147
|
case "help":
|
|
11250
|
-
return buildPmTools(connection,
|
|
11251
|
-
includePackTools: !!context?.isParentTask
|
|
11252
|
-
});
|
|
12148
|
+
return buildPmTools(connection, packToolsFor(context?.isParentTask));
|
|
11253
12149
|
default:
|
|
11254
12150
|
return config.mode === "pm" ? buildPmTools(connection, { includePackTools: false }) : [];
|
|
11255
12151
|
}
|
|
11256
12152
|
}
|
|
11257
12153
|
function buildPrGuideToolsFor(effectiveMode, connection, config, context) {
|
|
11258
12154
|
const isLeafBuild = effectiveMode === "building" || effectiveMode === "auto";
|
|
11259
|
-
|
|
12155
|
+
const isSinglePodPack = config.mode === "pack" && config.packExecution !== "fan-out";
|
|
12156
|
+
return (isLeafBuild || isSinglePodPack) && (isSinglePodPack || !context?.isParentTask) ? [
|
|
11260
12157
|
buildPublishReviewGuideTool(connection, {
|
|
11261
12158
|
resolveHeadSha: () => resolveGitHeadSha(config.workspaceDir)
|
|
11262
12159
|
})
|
|
@@ -11268,11 +12165,30 @@ var ALWAYS_LOADED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
11268
12165
|
"read_task_chat",
|
|
11269
12166
|
"get_task",
|
|
11270
12167
|
"get_current_plan",
|
|
12168
|
+
// The identity call the shared skills open with. Its schema is empty, so it
|
|
12169
|
+
// is the cheapest possible entry here, and deferring it would put a
|
|
12170
|
+
// ToolSearch round trip in front of the FIRST call of every skill run.
|
|
12171
|
+
"get_connection_context",
|
|
11271
12172
|
"set_manual_tests",
|
|
11272
12173
|
"create_pull_request",
|
|
11273
|
-
// Planning/auto/building
|
|
11274
|
-
|
|
12174
|
+
// Planning/auto/building.
|
|
12175
|
+
//
|
|
12176
|
+
// A2-6, executed HALFWAY on purpose. The note here used to say the split pair
|
|
12177
|
+
// should leave this set once the prompts stopped naming them. `update_task`
|
|
12178
|
+
// has now replaced every `update_task_plan` reference in the prompts, so that
|
|
12179
|
+
// one is gone from the always-loaded set — it stays REGISTERED, just not hot.
|
|
12180
|
+
//
|
|
12181
|
+
// `update_task_properties` does NOT leave, and the plan that asked for the
|
|
12182
|
+
// pair to go was wrong about it: `update_task`'s agent surface deliberately
|
|
12183
|
+
// omits `storyPointValue` and `risk` (see tool-contracts/task-update.ts), and
|
|
12184
|
+
// this is the only pod tool that carries them. Discovery's ExitPlanMode gate
|
|
12185
|
+
// fails until the card has story points, risk AND a title, so dropping this
|
|
12186
|
+
// would leave the one mode that must set them with no tool that can — the
|
|
12187
|
+
// unfollowable-instruction defect, manufactured deliberately.
|
|
11275
12188
|
"update_task_properties",
|
|
12189
|
+
// The shared-skill card write: one name whose fields match the mcp surface,
|
|
12190
|
+
// so a single skill text drives a local session and a pod verbatim.
|
|
12191
|
+
"update_task",
|
|
11276
12192
|
// Building/auto — the PR guide is published as part of opening the PR
|
|
11277
12193
|
"publish_review_guide",
|
|
11278
12194
|
// Review mode
|
|
@@ -11298,12 +12214,9 @@ var PACK_PROMOTED_TOOLS = /* @__PURE__ */ new Set(["get_execution_logs"]);
|
|
|
11298
12214
|
function glossaryToolsFor(connection, config, context) {
|
|
11299
12215
|
return context?.projectId ? buildGlossaryTools(connection, context.projectId, config.taskId, config.workspaceDir) : [];
|
|
11300
12216
|
}
|
|
11301
|
-
function
|
|
11302
|
-
return context?.projectId && context.googleDriveConnected ? buildDriveTools(connection, context.projectId) : [];
|
|
11303
|
-
}
|
|
11304
|
-
function promotedToolsFor(effectiveMode, isPack, isProjectAgent) {
|
|
12217
|
+
function promotedToolsFor(effectiveMode, isPack, isProjectAgent, isSinglePodPack = false) {
|
|
11305
12218
|
const names = /* @__PURE__ */ new Set();
|
|
11306
|
-
if (effectiveMode === "building" || effectiveMode === "auto") {
|
|
12219
|
+
if (effectiveMode === "building" || effectiveMode === "auto" || isSinglePodPack) {
|
|
11307
12220
|
for (const name of BUILDING_PROMOTED_TOOLS) names.add(name);
|
|
11308
12221
|
}
|
|
11309
12222
|
if (effectiveMode === "review") {
|
|
@@ -11332,7 +12245,7 @@ function buildConveyorTools(connection, config, context, agentMode) {
|
|
|
11332
12245
|
const handoffTools = config.mode === "pm" && (effectiveMode === "discovery" || effectiveMode === "auto") ? [buildHandoffTool(connection)] : [];
|
|
11333
12246
|
const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
|
|
11334
12247
|
const glossaryTools = glossaryToolsFor(connection, config, context);
|
|
11335
|
-
const
|
|
12248
|
+
const connectedTools = connectedToolsFor(connection, context);
|
|
11336
12249
|
const isPack = config.mode === "pack" || Boolean(context?.isParentTask);
|
|
11337
12250
|
return withAlwaysLoad(
|
|
11338
12251
|
[
|
|
@@ -11343,10 +12256,15 @@ function buildConveyorTools(connection, config, context, agentMode) {
|
|
|
11343
12256
|
...prGuideTools,
|
|
11344
12257
|
...handoffTools,
|
|
11345
12258
|
...glossaryTools,
|
|
11346
|
-
...
|
|
12259
|
+
...connectedTools,
|
|
11347
12260
|
...emergencyTools
|
|
11348
12261
|
],
|
|
11349
|
-
promotedToolsFor(
|
|
12262
|
+
promotedToolsFor(
|
|
12263
|
+
effectiveMode,
|
|
12264
|
+
isPack,
|
|
12265
|
+
config.mode === "pm",
|
|
12266
|
+
config.mode === "pack" && config.packExecution !== "fan-out"
|
|
12267
|
+
)
|
|
11350
12268
|
);
|
|
11351
12269
|
}
|
|
11352
12270
|
function createConveyorMcpServer(harness, connection, config, context, agentMode) {
|
|
@@ -11835,7 +12753,7 @@ function fallbackResetIso(rateLimitType, now = Date.now()) {
|
|
|
11835
12753
|
// src/execution/task-property-utils.ts
|
|
11836
12754
|
function collectMissingProps(taskProps) {
|
|
11837
12755
|
const missing = [];
|
|
11838
|
-
if (!taskProps.plan?.trim()) missing.push("plan (save via
|
|
12756
|
+
if (!taskProps.plan?.trim()) missing.push("plan (save via update_task)");
|
|
11839
12757
|
if (!taskProps.storyPointId) missing.push("story points (use update_task_properties)");
|
|
11840
12758
|
if (!taskProps.title || taskProps.title === "Untitled")
|
|
11841
12759
|
missing.push("title (use update_task_properties)");
|
|
@@ -12013,7 +12931,12 @@ function matchesReadOnlyBlocked(cmd) {
|
|
|
12013
12931
|
}
|
|
12014
12932
|
return null;
|
|
12015
12933
|
}
|
|
12934
|
+
var POST_CHANNEL_TOOL = /(^|__)post_channel_message$/;
|
|
12935
|
+
var CHANNEL_POST_DENIED = "Posting into a chat channel is not available in this mode. Reading channels still is \u2014 use read_channel_messages if you need the discussion.";
|
|
12016
12936
|
function handleReadOnlyToolAccess(toolName, input) {
|
|
12937
|
+
if (POST_CHANNEL_TOOL.test(toolName)) {
|
|
12938
|
+
return { behavior: "deny", message: CHANNEL_POST_DENIED };
|
|
12939
|
+
}
|
|
12017
12940
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
12018
12941
|
if (isPlanFile(input)) {
|
|
12019
12942
|
return { behavior: "allow", updatedInput: input };
|
|
@@ -12056,13 +12979,16 @@ function handleBuildingToolAccess(toolName, input) {
|
|
|
12056
12979
|
return { behavior: "allow", updatedInput: input };
|
|
12057
12980
|
}
|
|
12058
12981
|
function handleReviewToolAccess(toolName, input) {
|
|
12982
|
+
if (POST_CHANNEL_TOOL.test(toolName)) {
|
|
12983
|
+
return { behavior: "deny", message: CHANNEL_POST_DENIED };
|
|
12984
|
+
}
|
|
12059
12985
|
return handleBuildingToolAccess(toolName, input);
|
|
12060
12986
|
}
|
|
12061
12987
|
var CHAT_BLOCKED_BASH = /\bgit\s+push\b|\bgh\s+pr\b|\bhub\s+pull-request\b/;
|
|
12062
12988
|
var CREATE_PR_TOOL = /(^|__)create_pull_request$/;
|
|
12063
12989
|
var CHAT_PLAN_GATE_MESSAGE = [
|
|
12064
12990
|
"This chat card has no saved plan, so it has no PR path yet.",
|
|
12065
|
-
"If the conversation has turned into a development task, save a plan with
|
|
12991
|
+
"If the conversation has turned into a development task, save a plan with update_task first \u2014 that identifies the card and moves it to In Progress \u2014 then commit, push, and open the PR.",
|
|
12066
12992
|
"If it is still a conversation, deliver the work by attaching files to the card with upload_attachment instead."
|
|
12067
12993
|
].join(" ");
|
|
12068
12994
|
function isChatPrTool(toolName, input) {
|
|
@@ -12092,7 +13018,7 @@ function enforceMissingProps(host, input, missingProps) {
|
|
|
12092
13018
|
"Cannot exit plan mode. Required task properties are missing:",
|
|
12093
13019
|
...missingProps.map((p) => `- ${p}`),
|
|
12094
13020
|
"",
|
|
12095
|
-
"Fill these in using MCP tools (e.g.
|
|
13021
|
+
"Fill these in using MCP tools (e.g. update_task, update_task_properties), then call ExitPlanMode again.",
|
|
12096
13022
|
"",
|
|
12097
13023
|
"If you have a deliberate reason to proceed without them, you must explicitly bypass validation by calling ExitPlanMode with `bypassValidation: true` as a tool argument. Do not bypass unless the team has asked you to \u2014 it will be surfaced in chat."
|
|
12098
13024
|
].join("\n")
|
|
@@ -12510,7 +13436,7 @@ async function buildFollowUpPrompt(host, context, followUpContent) {
|
|
|
12510
13436
|
const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
|
|
12511
13437
|
(b) => b.type === "image"
|
|
12512
13438
|
);
|
|
12513
|
-
const textPrompt = isPmMode ? `${await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode)}
|
|
13439
|
+
const textPrompt = isPmMode ? `${await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode, host.config.packExecution)}
|
|
12514
13440
|
|
|
12515
13441
|
---
|
|
12516
13442
|
|
|
@@ -12707,7 +13633,8 @@ async function runPrefilledFollowUp(host, context, options, resume, followUpCont
|
|
|
12707
13633
|
host.config.mode,
|
|
12708
13634
|
context,
|
|
12709
13635
|
host.isAuto,
|
|
12710
|
-
host.agentMode
|
|
13636
|
+
host.agentMode,
|
|
13637
|
+
host.config.packExecution
|
|
12711
13638
|
);
|
|
12712
13639
|
queryOptions.appendSystemPrompt = [queryOptions.appendSystemPrompt, initialPrompt].filter(Boolean).join("\n\n").slice(0, APPEND_SYSTEM_PROMPT_MAX_CHARS);
|
|
12713
13640
|
}
|
|
@@ -12778,7 +13705,8 @@ async function runInitialQuery(host, context, options, resume, promptDelivery) {
|
|
|
12778
13705
|
host.config.mode,
|
|
12779
13706
|
context,
|
|
12780
13707
|
host.isAuto,
|
|
12781
|
-
host.agentMode
|
|
13708
|
+
host.agentMode,
|
|
13709
|
+
host.config.packExecution
|
|
12782
13710
|
);
|
|
12783
13711
|
const { prompt, appendSystemPrompt } = selectInitialPromptInput(
|
|
12784
13712
|
promptDelivery,
|
|
@@ -12808,7 +13736,13 @@ async function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
|
12808
13736
|
);
|
|
12809
13737
|
}
|
|
12810
13738
|
const retryPrompt = buildMultimodalPrompt(
|
|
12811
|
-
await buildInitialPrompt(
|
|
13739
|
+
await buildInitialPrompt(
|
|
13740
|
+
host.config.mode,
|
|
13741
|
+
context,
|
|
13742
|
+
host.isAuto,
|
|
13743
|
+
host.agentMode,
|
|
13744
|
+
host.config.packExecution
|
|
13745
|
+
),
|
|
12812
13746
|
context,
|
|
12813
13747
|
lastErrorWasImage || !supportsImageBlocks(host.harnessKind)
|
|
12814
13748
|
);
|
|
@@ -12837,7 +13771,13 @@ async function handleAuthError(context, host, options) {
|
|
|
12837
13771
|
context.claudeSessionId = null;
|
|
12838
13772
|
host.connection.storeSessionId("");
|
|
12839
13773
|
const freshPrompt = buildMultimodalPrompt(
|
|
12840
|
-
await buildInitialPrompt(
|
|
13774
|
+
await buildInitialPrompt(
|
|
13775
|
+
host.config.mode,
|
|
13776
|
+
context,
|
|
13777
|
+
host.isAuto,
|
|
13778
|
+
host.agentMode,
|
|
13779
|
+
host.config.packExecution
|
|
13780
|
+
),
|
|
12841
13781
|
context,
|
|
12842
13782
|
!supportsImageBlocks(host.harnessKind)
|
|
12843
13783
|
);
|
|
@@ -12852,7 +13792,13 @@ async function handleStaleSession(context, host, options) {
|
|
|
12852
13792
|
context.claudeSessionId = null;
|
|
12853
13793
|
host.connection.storeSessionId("");
|
|
12854
13794
|
const freshPrompt = buildMultimodalPrompt(
|
|
12855
|
-
await buildInitialPrompt(
|
|
13795
|
+
await buildInitialPrompt(
|
|
13796
|
+
host.config.mode,
|
|
13797
|
+
context,
|
|
13798
|
+
host.isAuto,
|
|
13799
|
+
host.agentMode,
|
|
13800
|
+
host.config.packExecution
|
|
13801
|
+
),
|
|
12856
13802
|
context,
|
|
12857
13803
|
!supportsImageBlocks(host.harnessKind)
|
|
12858
13804
|
);
|
|
@@ -12946,7 +13892,13 @@ async function handleUsageCapRejection(context, host, options, rateLimitType, re
|
|
|
12946
13892
|
context.claudeSessionId = null;
|
|
12947
13893
|
host.connection.storeSessionId("");
|
|
12948
13894
|
const freshPrompt = buildMultimodalPrompt(
|
|
12949
|
-
await buildInitialPrompt(
|
|
13895
|
+
await buildInitialPrompt(
|
|
13896
|
+
host.config.mode,
|
|
13897
|
+
context,
|
|
13898
|
+
host.isAuto,
|
|
13899
|
+
host.agentMode,
|
|
13900
|
+
host.config.packExecution
|
|
13901
|
+
),
|
|
12950
13902
|
context,
|
|
12951
13903
|
!supportsImageBlocks(host.harnessKind)
|
|
12952
13904
|
);
|
|
@@ -13778,9 +14730,20 @@ var BackgroundWorkTracker = class {
|
|
|
13778
14730
|
}
|
|
13779
14731
|
};
|
|
13780
14732
|
|
|
14733
|
+
// src/runner/live-children.ts
|
|
14734
|
+
function findLiveChild(sources) {
|
|
14735
|
+
for (const source of sources) {
|
|
14736
|
+
for (const sessionId of source.activeSessionIds()) {
|
|
14737
|
+
if (sessionId) return { kind: source.kind, sessionId };
|
|
14738
|
+
}
|
|
14739
|
+
}
|
|
14740
|
+
return null;
|
|
14741
|
+
}
|
|
14742
|
+
|
|
13781
14743
|
// src/runner/session-runner.ts
|
|
13782
14744
|
var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery", "chat"]);
|
|
13783
14745
|
var AUTONOMOUS_RUNNER_MODES = /* @__PURE__ */ new Set(["pack", "pm", "code-review"]);
|
|
14746
|
+
var CHILD_DEFER_RECHECK_MS = 60 * 1e3;
|
|
13784
14747
|
var SessionRunner = class _SessionRunner {
|
|
13785
14748
|
connection;
|
|
13786
14749
|
mode;
|
|
@@ -13852,6 +14815,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
13852
14815
|
this.connection.sendHeartbeat(this.loopLag.takeMaxLagMs(), loopStatus);
|
|
13853
14816
|
},
|
|
13854
14817
|
onIdleTimeout: () => {
|
|
14818
|
+
if (this.deferShutdownForLiveChild("idle")) return;
|
|
13855
14819
|
process.stderr.write("[conveyor-agent] Idle timeout reached, stopping agent\n");
|
|
13856
14820
|
this.stopped = true;
|
|
13857
14821
|
this.queryBridge?.stop();
|
|
@@ -13862,6 +14826,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
13862
14826
|
}
|
|
13863
14827
|
},
|
|
13864
14828
|
onDormantTimeout: () => {
|
|
14829
|
+
if (this.deferShutdownForLiveChild("dormant")) return;
|
|
13865
14830
|
process.stderr.write("[conveyor-agent] Dormant idle timeout reached, shutting down\n");
|
|
13866
14831
|
this.stopped = true;
|
|
13867
14832
|
this.queryBridge?.stop();
|
|
@@ -13879,6 +14844,58 @@ var SessionRunner = class _SessionRunner {
|
|
|
13879
14844
|
get state() {
|
|
13880
14845
|
return this._state;
|
|
13881
14846
|
}
|
|
14847
|
+
/**
|
|
14848
|
+
* Supervisors whose live children keep this pod alive. Empty until `cli.ts`
|
|
14849
|
+
* wires them, which is deliberate: the supervisors are constructed AFTER the
|
|
14850
|
+
* runner, and an empty list simply means "no children to protect" — the
|
|
14851
|
+
* pre-existing shutdown behavior.
|
|
14852
|
+
*/
|
|
14853
|
+
liveChildSources = [];
|
|
14854
|
+
/** Called by `cli.ts` once the child supervisors exist. */
|
|
14855
|
+
setLiveChildSources(sources) {
|
|
14856
|
+
this.liveChildSources = sources;
|
|
14857
|
+
}
|
|
14858
|
+
/**
|
|
14859
|
+
* Suppress an idle/dormant shutdown while a spawned child is still working.
|
|
14860
|
+
*
|
|
14861
|
+
* The Planner is the pod's main process, so its timeouts take the pod — and
|
|
14862
|
+
* the Builder's in-flight work — down with it. Returns true when the shutdown
|
|
14863
|
+
* was deferred and the caller must not proceed.
|
|
14864
|
+
*
|
|
14865
|
+
* Re-arms with a SHORT delay rather than a fresh full window: the pod should
|
|
14866
|
+
* converge on shutdown soon after the last child exits, not one more idle
|
|
14867
|
+
* window later. Bounded by the configured timeout so a test with a 50ms idle
|
|
14868
|
+
* window re-checks in 50ms rather than a minute.
|
|
14869
|
+
*
|
|
14870
|
+
* Fail-open by construction — if the probe throws, or no sources are wired,
|
|
14871
|
+
* the shutdown proceeds exactly as before. The opposite bias (a pod that
|
|
14872
|
+
* cannot die) is the more expensive mistake here only in money; killing a
|
|
14873
|
+
* live build costs work.
|
|
14874
|
+
*/
|
|
14875
|
+
deferShutdownForLiveChild(timer) {
|
|
14876
|
+
let live;
|
|
14877
|
+
try {
|
|
14878
|
+
live = findLiveChild(this.liveChildSources);
|
|
14879
|
+
} catch (err) {
|
|
14880
|
+
process.stderr.write(`[conveyor-agent] Live-child probe failed, not deferring: ${err}
|
|
14881
|
+
`);
|
|
14882
|
+
return false;
|
|
14883
|
+
}
|
|
14884
|
+
if (!live) return false;
|
|
14885
|
+
const recheckMs = Math.min(this.lifecycle.config.idleTimeoutMs, CHILD_DEFER_RECHECK_MS);
|
|
14886
|
+
const label = timer === "idle" ? "Idle" : "Dormant idle";
|
|
14887
|
+
process.stderr.write(
|
|
14888
|
+
`[conveyor-agent] ${label} timeout deferred: ${live.kind} session ${live.sessionId} active
|
|
14889
|
+
`
|
|
14890
|
+
);
|
|
14891
|
+
if (timer === "idle") {
|
|
14892
|
+
this.lifecycle.startIdleTimer(recheckMs);
|
|
14893
|
+
} else {
|
|
14894
|
+
this.dormantDeadline = Date.now() + recheckMs;
|
|
14895
|
+
this.lifecycle.startDormantTimer(recheckMs);
|
|
14896
|
+
}
|
|
14897
|
+
return true;
|
|
14898
|
+
}
|
|
13882
14899
|
get sessionId() {
|
|
13883
14900
|
return this.connection.sessionId;
|
|
13884
14901
|
}
|
|
@@ -14604,6 +15621,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
14604
15621
|
instructions: this.fullContext?.agentInstructions ?? "",
|
|
14605
15622
|
workspaceDir: this.config.workspaceDir,
|
|
14606
15623
|
mode: this.config.runnerMode,
|
|
15624
|
+
packExecution: this.config.packExecution,
|
|
14607
15625
|
isAuto: this.config.isAuto
|
|
14608
15626
|
};
|
|
14609
15627
|
const bridge = new QueryBridge(this.connection, this.mode, runnerConfig, {
|
|
@@ -14867,6 +15885,8 @@ function unshallowRepo(workspaceDir) {
|
|
|
14867
15885
|
|
|
14868
15886
|
export {
|
|
14869
15887
|
DEFAULT_SONNET_MODEL,
|
|
15888
|
+
RUNNER_MODES,
|
|
15889
|
+
parsePackExecution,
|
|
14870
15890
|
isPermissionDeniedError,
|
|
14871
15891
|
buildSynthesizedCredentials,
|
|
14872
15892
|
claudeJsonPath,
|