@drisp/cli 0.7.13 → 0.7.15
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/README.md +6 -0
- package/dist/{WorkflowInstallWizard-VPBOZJY5.js → WorkflowInstallWizard-MVTO43MW.js} +2 -2
- package/dist/{chunk-SACVOS6T.js → chunk-GGAPZULS.js} +388 -20
- package/dist/{chunk-CD6EY4ZE.js → chunk-PAPYHUIO.js} +2 -2
- package/dist/{chunk-5TM2ARN3.js → chunk-YXDCO7SI.js} +10407 -2680
- package/dist/cli.js +2757 -141
- package/dist/dashboard-daemon.js +3 -3
- package/dist/runner.js +3 -3
- package/dist/session-control-relay.js +24 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
RUN_EXIT_CODE,
|
|
10
10
|
ServiceNotEnabledError,
|
|
11
11
|
approvalAnswer,
|
|
12
|
+
assignmentPageSchema,
|
|
13
|
+
assignmentReportSchema,
|
|
14
|
+
assignmentRevisionSchema,
|
|
15
|
+
assignmentSchema,
|
|
12
16
|
attachRuntimeEventLoop,
|
|
13
17
|
attemptForecast,
|
|
14
18
|
behindMain,
|
|
@@ -20,18 +24,30 @@ import {
|
|
|
20
24
|
buildPostByToolUseId,
|
|
21
25
|
buildProbeConfigs,
|
|
22
26
|
calibrationSchema,
|
|
27
|
+
cancelProgress,
|
|
23
28
|
centerAnsi,
|
|
24
29
|
childWorktreePath,
|
|
25
30
|
classifyFailure,
|
|
31
|
+
commandProgress,
|
|
26
32
|
commandSchema,
|
|
27
33
|
commitBuildFiles,
|
|
28
34
|
compactText,
|
|
35
|
+
completionPackageSchema,
|
|
29
36
|
computeInputRows,
|
|
30
37
|
connectAlreadyConnectedSchema,
|
|
31
38
|
connectCodeResponseSchema,
|
|
32
39
|
connectPendingResponseSchema,
|
|
33
40
|
connectRedeemResponseSchema,
|
|
34
41
|
connectStartResponseSchema,
|
|
42
|
+
controlProgress,
|
|
43
|
+
controlRequestSchema,
|
|
44
|
+
coordinationActivityPageSchema,
|
|
45
|
+
coordinationBriefSchema,
|
|
46
|
+
coordinationCommandResultSchema,
|
|
47
|
+
coordinationCommandSchema,
|
|
48
|
+
coordinationInboxEventSchema,
|
|
49
|
+
coordinationSpendSchema,
|
|
50
|
+
coordinatorSchema,
|
|
35
51
|
createFeedMapper,
|
|
36
52
|
createLocalFeedSink,
|
|
37
53
|
createRuntime,
|
|
@@ -39,10 +55,26 @@ import {
|
|
|
39
55
|
credentialHelperKey,
|
|
40
56
|
cursorToVisualPosition,
|
|
41
57
|
defaultWorktreesRoot,
|
|
58
|
+
describeChecks,
|
|
42
59
|
detectClaudeVersion,
|
|
43
60
|
eligibilityReason,
|
|
44
61
|
ensureBuildWorktree,
|
|
45
62
|
ensureChildWorktree,
|
|
63
|
+
executionActivationSchema,
|
|
64
|
+
executionCancellationSchema,
|
|
65
|
+
executionControlState,
|
|
66
|
+
executionHierarchySchema,
|
|
67
|
+
executionMessageSchema,
|
|
68
|
+
executionOutcomeSchema,
|
|
69
|
+
executionPageSchema,
|
|
70
|
+
executionQuestionPageSchema,
|
|
71
|
+
executionQuestionSchema,
|
|
72
|
+
executionSchema,
|
|
73
|
+
executionStepsSchema,
|
|
74
|
+
executionWorkflowPageSchema,
|
|
75
|
+
executionWorkflowRevisionPageSchema,
|
|
76
|
+
executionWorkflowRevisionSchema,
|
|
77
|
+
executionWorkflowSchema,
|
|
46
78
|
expandHome,
|
|
47
79
|
extractFriendlyServerName,
|
|
48
80
|
extractPermissionSnapshot,
|
|
@@ -71,10 +103,12 @@ import {
|
|
|
71
103
|
historyRecordSchema,
|
|
72
104
|
hubHttpUrl,
|
|
73
105
|
identityHubOrigin,
|
|
106
|
+
inboxCursorSchema,
|
|
74
107
|
isBashToolResponse,
|
|
75
108
|
isCommandPrefix,
|
|
76
109
|
isGitHubRepository,
|
|
77
110
|
isScopedPermissionsRequest,
|
|
111
|
+
judgeChecks,
|
|
78
112
|
lastCommitTouching,
|
|
79
113
|
listAwaitingAttentionRuns,
|
|
80
114
|
listHarnessAdapters,
|
|
@@ -85,9 +119,11 @@ import {
|
|
|
85
119
|
lookupCredential,
|
|
86
120
|
machineInstallationId,
|
|
87
121
|
makeSkippedProbe,
|
|
122
|
+
maxClientCancelBoundaryGraceMs,
|
|
88
123
|
mergeFeedItems,
|
|
89
124
|
mergeGuidance,
|
|
90
125
|
messageGlyphs,
|
|
126
|
+
messageProgress,
|
|
91
127
|
overriddenIdentityPath,
|
|
92
128
|
parseFrontmatter,
|
|
93
129
|
parseToolName,
|
|
@@ -97,6 +133,9 @@ import {
|
|
|
97
133
|
probeSkipReason,
|
|
98
134
|
processRegistry,
|
|
99
135
|
progressGlyphs,
|
|
136
|
+
qaRoundPageSchema,
|
|
137
|
+
readCheckList,
|
|
138
|
+
recoveryEpisodeSchema,
|
|
100
139
|
register,
|
|
101
140
|
registerCleanupOnExit,
|
|
102
141
|
releaseMcpAsset,
|
|
@@ -112,8 +151,12 @@ import {
|
|
|
112
151
|
resolveHookForwarderCommand,
|
|
113
152
|
resolveRuntimeAuthOverlay,
|
|
114
153
|
resolveToolColumn,
|
|
154
|
+
reviewRoundPageSchema,
|
|
115
155
|
runClaudeAuthStatus,
|
|
116
156
|
runConfiguredRunner,
|
|
157
|
+
runControlCommandResultSchema,
|
|
158
|
+
runControlCommandSchema,
|
|
159
|
+
runControlRecordSchema,
|
|
117
160
|
runExec,
|
|
118
161
|
runMachineRunners,
|
|
119
162
|
runProbe,
|
|
@@ -130,12 +173,14 @@ import {
|
|
|
130
173
|
runnerSoftwareVersion,
|
|
131
174
|
runnerUpdateRequestSchema,
|
|
132
175
|
scopeFingerprint,
|
|
176
|
+
selectWorkPath,
|
|
133
177
|
serviceInstallationId,
|
|
134
178
|
sessionsDir,
|
|
135
179
|
sliceAnsi,
|
|
136
180
|
spaces,
|
|
137
181
|
startDashboardDecisionDrain,
|
|
138
182
|
startWorkflowExecution,
|
|
183
|
+
supportedOperations,
|
|
139
184
|
supportsSessionApproval,
|
|
140
185
|
teamDefaultRequestSchema,
|
|
141
186
|
teamDefaultSchema,
|
|
@@ -146,9 +191,113 @@ import {
|
|
|
146
191
|
trackerOperationSchema,
|
|
147
192
|
trackerSnapshotSchema,
|
|
148
193
|
visualPositionToOffset,
|
|
194
|
+
workActivityPageSchema,
|
|
195
|
+
workAttachmentCheckPageSchema,
|
|
196
|
+
workAttachmentPageSchema,
|
|
197
|
+
workAttachmentSchema,
|
|
198
|
+
workAttentionActivityPageSchema,
|
|
199
|
+
workAttentionCommandResultSchema,
|
|
200
|
+
workAttentionCommandSchema,
|
|
201
|
+
workAttentionRequestPageSchema,
|
|
202
|
+
workAttentionRequestSchema,
|
|
203
|
+
workAuthoritySchema,
|
|
204
|
+
workCandidatePageSchema,
|
|
205
|
+
workCandidateSchema,
|
|
206
|
+
workCandidateSelectionSchema,
|
|
207
|
+
workClientContract,
|
|
208
|
+
workClientContractHeader,
|
|
209
|
+
workClientUnsupported,
|
|
210
|
+
workCommandResultSchema,
|
|
211
|
+
workCommandSchema,
|
|
212
|
+
workCommentPageSchema,
|
|
213
|
+
workCommentRevisionPageSchema,
|
|
214
|
+
workCommentSchema,
|
|
215
|
+
workDeliveryMigrationPageSchema,
|
|
216
|
+
workDeliveryWorkflowPageSchema,
|
|
217
|
+
workDeliveryWorkflowRevisionPageSchema,
|
|
218
|
+
workDeliveryWorkflowRevisionSchema,
|
|
219
|
+
workDeliveryWorkflowSchema,
|
|
220
|
+
workDependencyGateSchema,
|
|
221
|
+
workDeploymentActivityPageSchema,
|
|
222
|
+
workDeploymentCommandResultSchema,
|
|
223
|
+
workDeploymentCommandSchema,
|
|
224
|
+
workDeploymentPageSchema,
|
|
225
|
+
workDeploymentSchema,
|
|
226
|
+
workDiscussionActivityPageSchema,
|
|
227
|
+
workDiscussionCommandResultSchema,
|
|
228
|
+
workDiscussionCommandSchema,
|
|
229
|
+
workEvidenceActivityPageSchema,
|
|
230
|
+
workEvidenceCommandResultSchema,
|
|
231
|
+
workEvidenceCommandSchema,
|
|
232
|
+
workEvidencePageSchema,
|
|
233
|
+
workEvidenceSchema,
|
|
234
|
+
workExecutionBindingPageSchema,
|
|
235
|
+
workExecutionBindingSchema,
|
|
236
|
+
workExecutionPolicySchema,
|
|
237
|
+
workExecutionReservationPageSchema,
|
|
238
|
+
workExecutionReservationSchema,
|
|
239
|
+
workImportBatchPageSchema,
|
|
240
|
+
workImportBatchSchema,
|
|
241
|
+
workImportCommandResultSchema,
|
|
242
|
+
workImportCommandSchema,
|
|
243
|
+
workImportComponentPageSchema,
|
|
244
|
+
workImportItemPageSchema,
|
|
245
|
+
workImportOutcomePageSchema,
|
|
246
|
+
workImportSnapshotSchema,
|
|
247
|
+
workIssueAssigneePageSchema,
|
|
248
|
+
workIssueLabelPageSchema,
|
|
249
|
+
workIssueMembershipSchema,
|
|
250
|
+
workIssuePageSchema,
|
|
251
|
+
workIssueRelationPageSchema,
|
|
252
|
+
workIssueRelationSchema,
|
|
253
|
+
workIssueReservationSchema,
|
|
254
|
+
workIssueSchema,
|
|
255
|
+
workIssueTransitionsSchema,
|
|
256
|
+
workLabelIssuePageSchema,
|
|
257
|
+
workLabelPageSchema,
|
|
258
|
+
workLabelSchema,
|
|
259
|
+
workLegacyAliasSchema,
|
|
260
|
+
workLifecycleEventPageSchema,
|
|
261
|
+
workMembershipActivityPageSchema,
|
|
262
|
+
workMembershipCommandResultSchema,
|
|
263
|
+
workMembershipCommandSchema,
|
|
264
|
+
workPageSchema,
|
|
265
|
+
workPendingRelationActivityPageSchema,
|
|
266
|
+
workPendingRelationCommandResultSchema,
|
|
267
|
+
workPendingRelationCommandSchema,
|
|
268
|
+
workPendingRelationPageSchema,
|
|
269
|
+
workPendingRelationSchema,
|
|
270
|
+
workPlanPageSchema,
|
|
271
|
+
workPlanSchema,
|
|
272
|
+
workProductionTrailPageSchema,
|
|
273
|
+
workRelationActivityPageSchema,
|
|
274
|
+
workRelationCommandResultSchema,
|
|
275
|
+
workRelationCommandSchema,
|
|
276
|
+
workReservationActivityPageSchema,
|
|
277
|
+
workReservationCommandResultSchema,
|
|
278
|
+
workReservationCommandSchema,
|
|
279
|
+
workScopeRevisionPageSchema,
|
|
280
|
+
workSourceActivityPageSchema,
|
|
281
|
+
workSourceAliasPageSchema,
|
|
282
|
+
workSourceAssociationPageSchema,
|
|
283
|
+
workSourceCommandResultSchema,
|
|
284
|
+
workSourceCommandSchema,
|
|
285
|
+
workSourceKeyLifecycleSchema,
|
|
286
|
+
workSourcePrincipalPageSchema,
|
|
287
|
+
workSourcePrincipalSchema,
|
|
288
|
+
workSourceReferencePageSchema,
|
|
289
|
+
workSourceReferenceSchema,
|
|
290
|
+
workSourceSpacePageSchema,
|
|
291
|
+
workSourceSpaceSchema,
|
|
292
|
+
workThreadSchema,
|
|
293
|
+
workTranscriptHistorySchema,
|
|
294
|
+
workTranscriptLinkPageSchema,
|
|
295
|
+
workTranscriptLinkSchema,
|
|
296
|
+
workVerificationAttemptPageSchema,
|
|
297
|
+
workVerificationAttemptSchema,
|
|
149
298
|
wrapText,
|
|
150
299
|
writeIdentityOnce
|
|
151
|
-
} from "./chunk-
|
|
300
|
+
} from "./chunk-YXDCO7SI.js";
|
|
152
301
|
import {
|
|
153
302
|
generateId as generateId2
|
|
154
303
|
} from "./chunk-PQIB66SG.js";
|
|
@@ -166,6 +315,7 @@ import {
|
|
|
166
315
|
CANNOT_PASS_TEMPLATE,
|
|
167
316
|
DESCRIPTION_TEMPLATE,
|
|
168
317
|
GATES,
|
|
318
|
+
GATE_NAMES,
|
|
169
319
|
LAYOUT_MARKERS,
|
|
170
320
|
MERGE_SIGN_OFF,
|
|
171
321
|
NO_GATE_TEXT_TEMPLATE,
|
|
@@ -176,6 +326,7 @@ import {
|
|
|
176
326
|
WORKFLOW_MD_TEMPLATE,
|
|
177
327
|
collectMcpServersWithOptions,
|
|
178
328
|
createSteerQueue,
|
|
329
|
+
describeIgnoredGate,
|
|
179
330
|
deserializeRunMemory,
|
|
180
331
|
external_exports,
|
|
181
332
|
findMarketplaceRepoDir,
|
|
@@ -189,6 +340,7 @@ import {
|
|
|
189
340
|
listMarketplaceWorkflows,
|
|
190
341
|
listMarketplaceWorkflowsFromRepo,
|
|
191
342
|
listWorkflows,
|
|
343
|
+
parseGateFlags,
|
|
192
344
|
parseWorkflowConfig,
|
|
193
345
|
projectConfigPath,
|
|
194
346
|
pullMarketplaceRepo,
|
|
@@ -200,6 +352,7 @@ import {
|
|
|
200
352
|
resolveWorkflow,
|
|
201
353
|
resolveWorkflowInstall,
|
|
202
354
|
resolveWorkflowMarketplaceSource,
|
|
355
|
+
tightenGates,
|
|
203
356
|
updateWorkflow,
|
|
204
357
|
updateWorkflows,
|
|
205
358
|
verifyRendered,
|
|
@@ -207,7 +360,7 @@ import {
|
|
|
207
360
|
workflowRegistryDir,
|
|
208
361
|
writeGlobalConfig,
|
|
209
362
|
writeProjectConfig
|
|
210
|
-
} from "./chunk-
|
|
363
|
+
} from "./chunk-GGAPZULS.js";
|
|
211
364
|
import {
|
|
212
365
|
__toESM
|
|
213
366
|
} from "./chunk-G3PMV62Z.js";
|
|
@@ -215,10 +368,10 @@ import {
|
|
|
215
368
|
// src/app/entry/cli.tsx
|
|
216
369
|
import { render } from "ink";
|
|
217
370
|
import meow from "meow";
|
|
218
|
-
import
|
|
371
|
+
import fs15 from "fs";
|
|
219
372
|
import { createRequire } from "module";
|
|
220
|
-
import
|
|
221
|
-
import
|
|
373
|
+
import os6 from "os";
|
|
374
|
+
import path16 from "path";
|
|
222
375
|
import { fileURLToPath } from "url";
|
|
223
376
|
|
|
224
377
|
// src/app/shell/sessionUiState.ts
|
|
@@ -642,7 +795,7 @@ function reduceSessionUiState(state, action, ctx) {
|
|
|
642
795
|
|
|
643
796
|
// src/app/shell/sessionUiController.ts
|
|
644
797
|
function createSessionUiController(update, context) {
|
|
645
|
-
const
|
|
798
|
+
const dispatch2 = (...actions) => update((state) => {
|
|
646
799
|
const ctx = context();
|
|
647
800
|
return resolveSessionUiState(
|
|
648
801
|
actions.reduce(
|
|
@@ -653,61 +806,61 @@ function createSessionUiController(update, context) {
|
|
|
653
806
|
);
|
|
654
807
|
});
|
|
655
808
|
return {
|
|
656
|
-
focus: (focusMode) =>
|
|
657
|
-
cycleFocus: () =>
|
|
809
|
+
focus: (focusMode) => dispatch2({ type: "set_focus_mode", focusMode }),
|
|
810
|
+
cycleFocus: () => dispatch2({ type: "cycle_focus" }),
|
|
658
811
|
input: (mode) => {
|
|
659
812
|
switch (mode) {
|
|
660
813
|
case "cancel":
|
|
661
|
-
|
|
814
|
+
dispatch2({ type: "cancel_input" });
|
|
662
815
|
break;
|
|
663
816
|
case "command-open":
|
|
664
|
-
|
|
817
|
+
dispatch2({ type: "open_command_input" });
|
|
665
818
|
break;
|
|
666
819
|
case "search-open":
|
|
667
|
-
|
|
820
|
+
dispatch2({ type: "open_search_input" });
|
|
668
821
|
break;
|
|
669
822
|
case "normal-open":
|
|
670
|
-
|
|
823
|
+
dispatch2({ type: "open_normal_input" });
|
|
671
824
|
break;
|
|
672
825
|
case "search":
|
|
673
826
|
case "command":
|
|
674
827
|
case "normal":
|
|
675
|
-
|
|
828
|
+
dispatch2({ type: "set_input_mode", inputMode: mode });
|
|
676
829
|
}
|
|
677
830
|
},
|
|
678
|
-
cycleHints: () =>
|
|
679
|
-
hideOverlay: () =>
|
|
831
|
+
cycleHints: () => dispatch2({ type: "cycle_hints_forced" }),
|
|
832
|
+
hideOverlay: () => dispatch2({ type: "set_show_run_overlay", show: false }),
|
|
680
833
|
search: {
|
|
681
|
-
edit: (query) =>
|
|
682
|
-
submit: (query, firstMatchIndex) =>
|
|
683
|
-
step: (direction, matches) =>
|
|
684
|
-
clear: () =>
|
|
834
|
+
edit: (query) => dispatch2({ type: "set_search_query", query }),
|
|
835
|
+
submit: (query, firstMatchIndex) => dispatch2({ type: "submit_search_query", query, firstMatchIndex }),
|
|
836
|
+
step: (direction, matches) => dispatch2({ type: "step_search_match", direction, matches }),
|
|
837
|
+
clear: () => dispatch2({ type: "clear_search_and_jump_tail" })
|
|
685
838
|
},
|
|
686
839
|
navigate: (panel, destination) => {
|
|
687
840
|
if (typeof destination === "number")
|
|
688
|
-
|
|
841
|
+
dispatch2(
|
|
689
842
|
panel === "feed" ? { type: "move_feed_cursor", delta: destination } : { type: "scroll_message_viewport", delta: destination }
|
|
690
843
|
);
|
|
691
844
|
else if (panel === "feed")
|
|
692
|
-
|
|
845
|
+
dispatch2({
|
|
693
846
|
type: destination === "top" ? "jump_feed_top" : "jump_feed_tail"
|
|
694
847
|
});
|
|
695
848
|
else
|
|
696
|
-
|
|
849
|
+
dispatch2({
|
|
697
850
|
type: destination === "top" ? "jump_message_top" : "jump_message_tail"
|
|
698
851
|
});
|
|
699
852
|
},
|
|
700
853
|
// Wheel navigation selects its panel and moves it in the same update.
|
|
701
|
-
scrollPanel: (panel, delta) =>
|
|
854
|
+
scrollPanel: (panel, delta) => dispatch2(
|
|
702
855
|
{ type: "set_focus_mode", focusMode: panel },
|
|
703
856
|
panel === "feed" ? { type: "move_feed_cursor", delta } : { type: "scroll_message_viewport", delta }
|
|
704
857
|
),
|
|
705
|
-
reveal: (cursor) =>
|
|
706
|
-
messageTab: (tab) =>
|
|
858
|
+
reveal: (cursor) => dispatch2({ type: "reveal_feed_entry", cursor }),
|
|
859
|
+
messageTab: (tab) => dispatch2({ type: "set_message_tab", tab }),
|
|
707
860
|
todo: {
|
|
708
|
-
toggle: () =>
|
|
709
|
-
move: (delta) =>
|
|
710
|
-
show: () =>
|
|
861
|
+
toggle: () => dispatch2({ type: "toggle_todo_visible" }),
|
|
862
|
+
move: (delta) => dispatch2({ type: "move_todo_cursor", delta }),
|
|
863
|
+
show: () => dispatch2({ type: "set_todo_visible", visible: true })
|
|
711
864
|
}
|
|
712
865
|
};
|
|
713
866
|
}
|
|
@@ -2811,7 +2964,7 @@ function useWorkflowSessionController(base, input) {
|
|
|
2811
2964
|
}
|
|
2812
2965
|
setIsRunning(false);
|
|
2813
2966
|
}, [base, cancelCurrentRun]);
|
|
2814
|
-
const
|
|
2967
|
+
const spawn2 = useCallback6(
|
|
2815
2968
|
async (prompt, continuation, _configOverride) => {
|
|
2816
2969
|
await cancelCurrentRun();
|
|
2817
2970
|
const latest = input.store?.getLatestRun();
|
|
@@ -2883,7 +3036,7 @@ function useWorkflowSessionController(base, input) {
|
|
|
2883
3036
|
}, []);
|
|
2884
3037
|
return {
|
|
2885
3038
|
...base,
|
|
2886
|
-
startTurn:
|
|
3039
|
+
startTurn: spawn2,
|
|
2887
3040
|
isRunning,
|
|
2888
3041
|
interrupt,
|
|
2889
3042
|
kill,
|
|
@@ -3791,7 +3944,7 @@ function textInputReducer(state, action) {
|
|
|
3791
3944
|
}
|
|
3792
3945
|
function useTextInput(options = {}) {
|
|
3793
3946
|
const { onChange, onSubmit, isActive = true } = options;
|
|
3794
|
-
const [state,
|
|
3947
|
+
const [state, dispatch2] = useReducer(textInputReducer, {
|
|
3795
3948
|
value: "",
|
|
3796
3949
|
cursorOffset: 0
|
|
3797
3950
|
});
|
|
@@ -3810,7 +3963,7 @@ function useTextInput(options = {}) {
|
|
|
3810
3963
|
onChangeRef.current?.(state.value);
|
|
3811
3964
|
}, [state.value]);
|
|
3812
3965
|
const setValue = useCallback7((newValue) => {
|
|
3813
|
-
|
|
3966
|
+
dispatch2({ type: "set-value", value: newValue });
|
|
3814
3967
|
}, []);
|
|
3815
3968
|
const { stdin } = useStdin();
|
|
3816
3969
|
const isForwardDeleteRef = useRef5(false);
|
|
@@ -3850,47 +4003,47 @@ function useTextInput(options = {}) {
|
|
|
3850
4003
|
if (key.return) {
|
|
3851
4004
|
const { value: val, cursorOffset: cur } = stateRef.current;
|
|
3852
4005
|
if (cur > 0 && val[cur - 1] === "\\") {
|
|
3853
|
-
|
|
4006
|
+
dispatch2({ type: "newline-escape" });
|
|
3854
4007
|
} else {
|
|
3855
4008
|
onSubmitRef.current?.(val);
|
|
3856
4009
|
}
|
|
3857
4010
|
return;
|
|
3858
4011
|
}
|
|
3859
4012
|
if (key.leftArrow) {
|
|
3860
|
-
|
|
4013
|
+
dispatch2(key.ctrl ? { type: "move-home" } : { type: "move-left" });
|
|
3861
4014
|
return;
|
|
3862
4015
|
}
|
|
3863
4016
|
if (key.rightArrow) {
|
|
3864
|
-
|
|
4017
|
+
dispatch2(key.ctrl ? { type: "move-end" } : { type: "move-right" });
|
|
3865
4018
|
return;
|
|
3866
4019
|
}
|
|
3867
4020
|
if (key.home) {
|
|
3868
|
-
|
|
4021
|
+
dispatch2({ type: "move-home" });
|
|
3869
4022
|
return;
|
|
3870
4023
|
}
|
|
3871
4024
|
if (key.end) {
|
|
3872
|
-
|
|
4025
|
+
dispatch2({ type: "move-end" });
|
|
3873
4026
|
return;
|
|
3874
4027
|
}
|
|
3875
4028
|
if (key.ctrl) {
|
|
3876
|
-
if (input === "a")
|
|
3877
|
-
else if (input === "e")
|
|
3878
|
-
else if (input === "w")
|
|
3879
|
-
else if (input === "u")
|
|
3880
|
-
else if (input === "d")
|
|
4029
|
+
if (input === "a") dispatch2({ type: "move-home" });
|
|
4030
|
+
else if (input === "e") dispatch2({ type: "move-end" });
|
|
4031
|
+
else if (input === "w") dispatch2({ type: "delete-word-back" });
|
|
4032
|
+
else if (input === "u") dispatch2({ type: "clear-line" });
|
|
4033
|
+
else if (input === "d") dispatch2({ type: "delete-forward" });
|
|
3881
4034
|
return;
|
|
3882
4035
|
}
|
|
3883
4036
|
if (key.backspace || key.delete) {
|
|
3884
4037
|
if (isForwardDeleteRef.current) {
|
|
3885
4038
|
isForwardDeleteRef.current = false;
|
|
3886
|
-
|
|
4039
|
+
dispatch2({ type: "delete-forward" });
|
|
3887
4040
|
} else {
|
|
3888
|
-
|
|
4041
|
+
dispatch2({ type: "backspace" });
|
|
3889
4042
|
}
|
|
3890
4043
|
return;
|
|
3891
4044
|
}
|
|
3892
4045
|
if (input && !key.meta) {
|
|
3893
|
-
|
|
4046
|
+
dispatch2({ type: "insert", char: input });
|
|
3894
4047
|
}
|
|
3895
4048
|
} finally {
|
|
3896
4049
|
done();
|
|
@@ -3902,7 +4055,7 @@ function useTextInput(options = {}) {
|
|
|
3902
4055
|
value: state.value,
|
|
3903
4056
|
cursorOffset: state.cursorOffset,
|
|
3904
4057
|
setValue,
|
|
3905
|
-
dispatch
|
|
4058
|
+
dispatch: dispatch2
|
|
3906
4059
|
};
|
|
3907
4060
|
}
|
|
3908
4061
|
|
|
@@ -4007,7 +4160,7 @@ var ShellInputImpl = forwardRef(function ShellInput({
|
|
|
4007
4160
|
commandSuggestionsEnabled,
|
|
4008
4161
|
wrapSuggestionLine
|
|
4009
4162
|
}, ref) {
|
|
4010
|
-
const { value, cursorOffset, setValue, dispatch } = useTextInput({
|
|
4163
|
+
const { value, cursorOffset, setValue, dispatch: dispatch2 } = useTextInput({
|
|
4011
4164
|
onChange,
|
|
4012
4165
|
onSubmit,
|
|
4013
4166
|
isActive: isInputActive
|
|
@@ -4132,7 +4285,7 @@ var ShellInputImpl = forwardRef(function ShellInput({
|
|
|
4132
4285
|
const recalled = historyBackRef.current?.(currentValue);
|
|
4133
4286
|
if (recalled !== void 0) applyProgrammaticValue(recalled);
|
|
4134
4287
|
} else {
|
|
4135
|
-
|
|
4288
|
+
dispatch2({ type: "move-up", width: inputContentWidth });
|
|
4136
4289
|
}
|
|
4137
4290
|
return;
|
|
4138
4291
|
}
|
|
@@ -4146,11 +4299,11 @@ var ShellInputImpl = forwardRef(function ShellInput({
|
|
|
4146
4299
|
const recalled = historyForwardRef.current?.();
|
|
4147
4300
|
if (recalled !== void 0) applyProgrammaticValue(recalled);
|
|
4148
4301
|
} else {
|
|
4149
|
-
|
|
4302
|
+
dispatch2({ type: "move-down", width: inputContentWidth });
|
|
4150
4303
|
}
|
|
4151
4304
|
}
|
|
4152
4305
|
},
|
|
4153
|
-
[applyProgrammaticValue,
|
|
4306
|
+
[applyProgrammaticValue, dispatch2, inputContentWidth, showSuggestions]
|
|
4154
4307
|
);
|
|
4155
4308
|
useInput7(handleArrows, { isActive: isInputActive });
|
|
4156
4309
|
const lines = renderInputLines(
|
|
@@ -5337,14 +5490,14 @@ function extractWebSearch(_input, response) {
|
|
|
5337
5490
|
return { type: "text", content: extractTextContent(response), maxLines: 10 };
|
|
5338
5491
|
}
|
|
5339
5492
|
function extractNotebookEdit(input, _response) {
|
|
5340
|
-
const
|
|
5493
|
+
const path17 = typeof input["notebook_path"] === "string" ? input["notebook_path"] : "";
|
|
5341
5494
|
const mode = typeof input["edit_mode"] === "string" ? input["edit_mode"] : "replace";
|
|
5342
5495
|
const source = typeof input["new_source"] === "string" ? input["new_source"] : "";
|
|
5343
|
-
if (!source) return { type: "text", content: `${mode} cell in ${
|
|
5496
|
+
if (!source) return { type: "text", content: `${mode} cell in ${path17}` };
|
|
5344
5497
|
return {
|
|
5345
5498
|
type: "code",
|
|
5346
5499
|
content: source,
|
|
5347
|
-
language: detectLanguage(
|
|
5500
|
+
language: detectLanguage(path17),
|
|
5348
5501
|
maxLines: 10
|
|
5349
5502
|
};
|
|
5350
5503
|
}
|
|
@@ -5870,10 +6023,10 @@ function extractToolSubject(toolName, toolInput) {
|
|
|
5870
6023
|
const parts = [];
|
|
5871
6024
|
const pattern = str(input, "pattern");
|
|
5872
6025
|
const glob = str(input, "glob");
|
|
5873
|
-
const
|
|
6026
|
+
const path17 = str(input, "path");
|
|
5874
6027
|
if (pattern) parts.push(`"${pattern}"`);
|
|
5875
6028
|
if (glob) parts.push(glob);
|
|
5876
|
-
if (
|
|
6029
|
+
if (path17) parts.push(`in ${path17}`);
|
|
5877
6030
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
5878
6031
|
}
|
|
5879
6032
|
case "Skill":
|
|
@@ -5906,9 +6059,13 @@ function eventLabel(event) {
|
|
|
5906
6059
|
return event.data.scope === "subagent" ? "Subagent" : "Agent";
|
|
5907
6060
|
case "user.prompt":
|
|
5908
6061
|
return "User";
|
|
6062
|
+
case "system.wake":
|
|
6063
|
+
return "Wake";
|
|
5909
6064
|
case "subagent.start":
|
|
5910
6065
|
case "subagent.stop":
|
|
5911
6066
|
return `Subagent \xB7 ${event.data.agent_type}`;
|
|
6067
|
+
case "subagent.task":
|
|
6068
|
+
return `Subagent \xB7 ${event.data.subagent_type ?? event.data.task_id}`;
|
|
5912
6069
|
case "run.start":
|
|
5913
6070
|
return "Run Start";
|
|
5914
6071
|
case "run.end":
|
|
@@ -6018,11 +6175,17 @@ function renderDetailLines(event, width, pairedPostEvent, theme = darkTheme) {
|
|
|
6018
6175
|
const content = renderMarkdownToLines(event.data.message, cw);
|
|
6019
6176
|
return buildResult(header, content, false);
|
|
6020
6177
|
}
|
|
6021
|
-
case "user.prompt":
|
|
6178
|
+
case "user.prompt":
|
|
6179
|
+
case "system.wake": {
|
|
6022
6180
|
const header = buildCompactHeader(event, width, { theme });
|
|
6023
6181
|
const content = renderMarkdownToLines(event.data.prompt, cw);
|
|
6024
6182
|
return buildResult(header, content, false);
|
|
6025
6183
|
}
|
|
6184
|
+
case "subagent.task": {
|
|
6185
|
+
const header = buildCompactHeader(event, width, { theme });
|
|
6186
|
+
const json2 = JSON.stringify(event.data, null, 2);
|
|
6187
|
+
return buildResult(header, highlightCode(json2, cw, "json"), true);
|
|
6188
|
+
}
|
|
6026
6189
|
case "tool.post":
|
|
6027
6190
|
case "tool.delta":
|
|
6028
6191
|
case "tool.failure":
|
|
@@ -8444,8 +8607,8 @@ function formatStatsSnapshot(snapshot) {
|
|
|
8444
8607
|
function truncateSessionId(id, maxWidth) {
|
|
8445
8608
|
if (id.length <= maxWidth) return id;
|
|
8446
8609
|
if (maxWidth >= 12) {
|
|
8447
|
-
const
|
|
8448
|
-
return id.slice(0, maxWidth - 7) + "\u2026" +
|
|
8610
|
+
const tail2 = id.slice(-6);
|
|
8611
|
+
return id.slice(0, maxWidth - 7) + "\u2026" + tail2;
|
|
8449
8612
|
}
|
|
8450
8613
|
const alphanumeric = id.replace(/[^a-zA-Z0-9]/g, "").slice(-4);
|
|
8451
8614
|
return "S" + (alphanumeric || "\u2013");
|
|
@@ -9955,10 +10118,11 @@ function deriveStartupTimeoutFailure(args) {
|
|
|
9955
10118
|
|
|
9956
10119
|
// src/app/shell/resolveAllowedTools.ts
|
|
9957
10120
|
var BASELINE_ALLOWED_TOOLS = ["mcp__*"];
|
|
9958
|
-
function resolveAllowedTools(harness, allowedTools) {
|
|
10121
|
+
function resolveAllowedTools(harness, allowedTools, workflowAllowedTools = []) {
|
|
9959
10122
|
const extras = [
|
|
9960
10123
|
...BASELINE_ALLOWED_TOOLS,
|
|
9961
|
-
...resolveHarnessAdapter(harness).capabilities.extraAllowedTools
|
|
10124
|
+
...resolveHarnessAdapter(harness).capabilities.extraAllowedTools,
|
|
10125
|
+
...workflowAllowedTools
|
|
9962
10126
|
];
|
|
9963
10127
|
const merged = [...allowedTools ?? []];
|
|
9964
10128
|
for (const tool of extras) {
|
|
@@ -11780,7 +11944,8 @@ function App({
|
|
|
11780
11944
|
workflow: runtimeState.workflow,
|
|
11781
11945
|
allowedTools: resolveAllowedTools(
|
|
11782
11946
|
runtimeState.harness,
|
|
11783
|
-
runtimeState.isolation?.allowedTools
|
|
11947
|
+
runtimeState.isolation?.allowedTools,
|
|
11948
|
+
runtimeState.workflow?.allowedTools
|
|
11784
11949
|
),
|
|
11785
11950
|
athenaSessionId,
|
|
11786
11951
|
children: /* @__PURE__ */ jsx23(
|
|
@@ -12210,6 +12375,19 @@ async function runExecCommand(input, deps = {}) {
|
|
|
12210
12375
|
);
|
|
12211
12376
|
return RUN_EXIT_CODE.USAGE;
|
|
12212
12377
|
}
|
|
12378
|
+
let buildGates;
|
|
12379
|
+
try {
|
|
12380
|
+
buildGates = parseGateFlags(input.flags.gates);
|
|
12381
|
+
} catch (error) {
|
|
12382
|
+
logError(`Error: ${error.message}`);
|
|
12383
|
+
return RUN_EXIT_CODE.USAGE;
|
|
12384
|
+
}
|
|
12385
|
+
const gates = tightenGates(input.runtimeConfig.gates, buildGates);
|
|
12386
|
+
for (const gate of gates.ignored) {
|
|
12387
|
+
logError(
|
|
12388
|
+
`Warning: ${describeIgnoredGate({ gate, source: "--gate", floor: "configured" })}`
|
|
12389
|
+
);
|
|
12390
|
+
}
|
|
12213
12391
|
let continueResolution;
|
|
12214
12392
|
try {
|
|
12215
12393
|
continueResolution = resolveResumeTarget({
|
|
@@ -12262,6 +12440,16 @@ async function runExecCommand(input, deps = {}) {
|
|
|
12262
12440
|
ephemeral: input.flags.ephemeral,
|
|
12263
12441
|
timeoutMs: input.flags.timeoutMs,
|
|
12264
12442
|
permissionGraceMs: graceMs ?? input.runtimeConfig.permissionGraceMs,
|
|
12443
|
+
gates: gates.gates,
|
|
12444
|
+
gateProvenance: {
|
|
12445
|
+
...input.runtimeConfig.settingsProvenance?.gates,
|
|
12446
|
+
// A gate --gate tightened was set by this Build.
|
|
12447
|
+
...Object.fromEntries(
|
|
12448
|
+
GATE_NAMES.filter(
|
|
12449
|
+
(name) => gates.gates[name] !== input.runtimeConfig.gates[name]
|
|
12450
|
+
).map((name) => [name, "build"])
|
|
12451
|
+
)
|
|
12452
|
+
},
|
|
12265
12453
|
...answer !== void 0 ? { storedAnswer: localAnswerDecision(answer) } : {},
|
|
12266
12454
|
// Reporting-only summary: strip to name + source layer so secret-bearing
|
|
12267
12455
|
// MCP env/command/args and skill paths never reach the startup notice or
|
|
@@ -13104,7 +13292,7 @@ var thisMachine = (installationId) => ({
|
|
|
13104
13292
|
os: `${platform()} ${release()}`.slice(0, 64),
|
|
13105
13293
|
softwareVersion: runnerSoftwareVersion
|
|
13106
13294
|
});
|
|
13107
|
-
var post = (origin,
|
|
13295
|
+
var post = (origin, path17, body) => fetch(new URL(path17, origin), {
|
|
13108
13296
|
method: "POST",
|
|
13109
13297
|
headers: { "Content-Type": "application/json" },
|
|
13110
13298
|
body: JSON.stringify(body)
|
|
@@ -13123,8 +13311,8 @@ async function alreadyConnected(existing, { forTeam, ownIdentity }) {
|
|
|
13123
13311
|
const problem = own ? await credentialAccepted(own.config) ? void 0 : "lapsed" : forTeam(existing.orgId).length ? "other_runner" : "missing";
|
|
13124
13312
|
const runner = { orgId: existing.orgId, runnerId: existing.runnerId, name: existing.name };
|
|
13125
13313
|
if (own && problem === void 0) return { status: "already_connected", ...runner, path: own.path };
|
|
13126
|
-
const
|
|
13127
|
-
return { status: "identity_unusable", ...runner, problem, ...
|
|
13314
|
+
const path17 = (own ?? forTeam(existing.orgId)[0])?.path;
|
|
13315
|
+
return { status: "identity_unusable", ...runner, problem, ...path17 ? { path: path17 } : {} };
|
|
13128
13316
|
}
|
|
13129
13317
|
async function connectWithCode({
|
|
13130
13318
|
code,
|
|
@@ -13196,20 +13384,20 @@ async function saveIdentity(origin, credential, { single, forTeam }, directory)
|
|
|
13196
13384
|
name: credential.name
|
|
13197
13385
|
});
|
|
13198
13386
|
const replaced = [];
|
|
13199
|
-
for (const { path:
|
|
13200
|
-
const aside = `${
|
|
13201
|
-
await rename(
|
|
13387
|
+
for (const { path: path18 } of forTeam(credential.orgId)) {
|
|
13388
|
+
const aside = `${path18}.replaced-${Date.now()}`;
|
|
13389
|
+
await rename(path18, aside);
|
|
13202
13390
|
replaced.push(aside);
|
|
13203
13391
|
}
|
|
13204
|
-
const
|
|
13392
|
+
const path17 = single ?? teamIdentityPath(origin, credential.orgId, directory);
|
|
13205
13393
|
try {
|
|
13206
|
-
await writeIdentityOnce(
|
|
13394
|
+
await writeIdentityOnce(path17, JSON.stringify(config, null, 2) + "\n");
|
|
13207
13395
|
} catch (error) {
|
|
13208
13396
|
if (error.code === "EEXIST")
|
|
13209
|
-
throw new Error(`Another Connect wrote a Runner identity for this Team at ${
|
|
13397
|
+
throw new Error(`Another Connect wrote a Runner identity for this Team at ${path17} meanwhile; it was left unchanged.`);
|
|
13210
13398
|
throw error;
|
|
13211
13399
|
}
|
|
13212
|
-
return { status: "connected", config, path:
|
|
13400
|
+
return { status: "connected", config, path: path17, replaced };
|
|
13213
13401
|
}
|
|
13214
13402
|
function pathSegment(value) {
|
|
13215
13403
|
const segment = encodeURIComponent(value);
|
|
@@ -13249,7 +13437,7 @@ async function refuseExisting(target) {
|
|
|
13249
13437
|
throw new Error(`This machine already has a Runner${name ? ` (${name})` : ""} configured at ${target}.`);
|
|
13250
13438
|
}
|
|
13251
13439
|
async function disconnectRunner({
|
|
13252
|
-
identity: { path:
|
|
13440
|
+
identity: { path: path17, config },
|
|
13253
13441
|
serviceManager,
|
|
13254
13442
|
othersRemain
|
|
13255
13443
|
}) {
|
|
@@ -13282,7 +13470,7 @@ async function disconnectRunner({
|
|
|
13282
13470
|
await restore();
|
|
13283
13471
|
throw new Error(`The Hub refused to disconnect ${name} (${body.error ?? response.status}); it is still connected.`);
|
|
13284
13472
|
}
|
|
13285
|
-
await forgetRunner(
|
|
13473
|
+
await forgetRunner(path17);
|
|
13286
13474
|
if (othersRemain) await restore();
|
|
13287
13475
|
else await serviceManager?.remove();
|
|
13288
13476
|
return result;
|
|
@@ -13435,18 +13623,18 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13435
13623
|
`This machine is already connected to this Team as ${outcome.name}, but ${why}. ` + (code ? `The code was not used: connect a new Runner in its place with ${retry}. ` : `Connect a new Runner in its place with a Connect code from Settings \u2192 Runners: ${retry}. `) + `The old ${outcome.name} stays listed in Settings \u2192 Runners, offline.`
|
|
13436
13624
|
);
|
|
13437
13625
|
}
|
|
13438
|
-
const { config, path:
|
|
13626
|
+
const { config, path: path17, replaced } = outcome;
|
|
13439
13627
|
for (const aside of replaced)
|
|
13440
13628
|
process.stdout.write(
|
|
13441
13629
|
`The Hub no longer recognised this machine's earlier identity for this Team; it was moved aside to ${aside}.
|
|
13442
13630
|
`
|
|
13443
13631
|
);
|
|
13444
13632
|
process.stdout.write(
|
|
13445
|
-
`Connected as ${config.name ?? config.runnerId}. Identity saved to ${
|
|
13633
|
+
`Connected as ${config.name ?? config.runnerId}. Identity saved to ${path17}.
|
|
13446
13634
|
`
|
|
13447
13635
|
);
|
|
13448
13636
|
if (services) {
|
|
13449
|
-
const service = await ensureService(services, config,
|
|
13637
|
+
const service = await ensureService(services, config, path17);
|
|
13450
13638
|
process.stdout.write(`${serviceStarted(service)}
|
|
13451
13639
|
`);
|
|
13452
13640
|
return 0;
|
|
@@ -13455,7 +13643,7 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13455
13643
|
process.stdout.write(
|
|
13456
13644
|
"No launchd or systemd here to run the Runner as a background service; running it in the foreground instead.\n"
|
|
13457
13645
|
);
|
|
13458
|
-
await runConfiguredRunner(
|
|
13646
|
+
await runConfiguredRunner(path17, options.signal);
|
|
13459
13647
|
return 0;
|
|
13460
13648
|
}
|
|
13461
13649
|
case "disconnect": {
|
|
@@ -13526,9 +13714,9 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13526
13714
|
configured: true,
|
|
13527
13715
|
// One entry per Team this machine serves; credentials are never printed.
|
|
13528
13716
|
teams: identities.map(
|
|
13529
|
-
({ path:
|
|
13717
|
+
({ path: path17, config: { token: _token, ...config } }) => ({
|
|
13530
13718
|
...config,
|
|
13531
|
-
path:
|
|
13719
|
+
path: path17
|
|
13532
13720
|
})
|
|
13533
13721
|
),
|
|
13534
13722
|
service: service ? {
|
|
@@ -13836,7 +14024,368 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
13836
14024
|
import fs7 from "fs";
|
|
13837
14025
|
import path7 from "path";
|
|
13838
14026
|
|
|
14027
|
+
// ../../packages/client/src/work-flows.ts
|
|
14028
|
+
var WorkFlowError = class extends Error {
|
|
14029
|
+
constructor(code, details = {}) {
|
|
14030
|
+
super(code);
|
|
14031
|
+
this.code = code;
|
|
14032
|
+
this.details = details;
|
|
14033
|
+
}
|
|
14034
|
+
code;
|
|
14035
|
+
details;
|
|
14036
|
+
};
|
|
14037
|
+
var execution = (result) => {
|
|
14038
|
+
const o = result.outcome;
|
|
14039
|
+
if (o.kind === "execution" || o.kind === "assignment" || o.kind === "package" || o.kind === "effect") return o.execution;
|
|
14040
|
+
throw new WorkFlowError("unexpected_outcome", { kind: o.kind });
|
|
14041
|
+
};
|
|
14042
|
+
var WorkFlows = class {
|
|
14043
|
+
constructor(client2, teamId, commandId = () => crypto.randomUUID(), pageSize = 20) {
|
|
14044
|
+
this.client = client2;
|
|
14045
|
+
this.teamId = teamId;
|
|
14046
|
+
this.commandId = commandId;
|
|
14047
|
+
this.pageSize = pageSize;
|
|
14048
|
+
}
|
|
14049
|
+
client;
|
|
14050
|
+
teamId;
|
|
14051
|
+
commandId;
|
|
14052
|
+
pageSize;
|
|
14053
|
+
/**
|
|
14054
|
+
* The Team's path. A Hub without the authority route (`not_found` with HTTP 404) predates this contract and has no
|
|
14055
|
+
* execution switch, so it is `legacy`; every other failure is thrown.
|
|
14056
|
+
*/
|
|
14057
|
+
async path() {
|
|
14058
|
+
try {
|
|
14059
|
+
return selectWorkPath(await this.client.workAuthority());
|
|
14060
|
+
} catch (error) {
|
|
14061
|
+
if (error instanceof ClientError && error.code === "not_found" && error.status === 404) return selectWorkPath(null);
|
|
14062
|
+
throw error;
|
|
14063
|
+
}
|
|
14064
|
+
}
|
|
14065
|
+
async operations() {
|
|
14066
|
+
return supportedOperations(await this.path());
|
|
14067
|
+
}
|
|
14068
|
+
/** Refuses (before any write) an operation the Team's path does not give canonical clients. */
|
|
14069
|
+
async require(operation) {
|
|
14070
|
+
const path17 = await this.path();
|
|
14071
|
+
if (path17.kind === "unsupported") throw new WorkFlowError(path17.code, { minimum: path17.minimum, current: path17.current });
|
|
14072
|
+
if (!supportedOperations(path17).includes(operation))
|
|
14073
|
+
throw new WorkFlowError(path17.execution === "legacy" && path17.work === "core" && !operation.startsWith("work.") && operation !== "history.read" ? "execution_authority_legacy" : "work_authority_legacy", { path: path17.kind, operation });
|
|
14074
|
+
}
|
|
14075
|
+
/** Names an operation the clients deliberately do not offer, with the reason; `undefined` when it is offered. */
|
|
14076
|
+
static unsupported(type) {
|
|
14077
|
+
return workClientUnsupported[type];
|
|
14078
|
+
}
|
|
14079
|
+
async send(body, commandId) {
|
|
14080
|
+
return this.client.coordinationCommand({ version: 1, teamId: this.teamId, commandId: commandId ?? this.commandId(), ...body });
|
|
14081
|
+
}
|
|
14082
|
+
// ── Reads: canonical Work records, one page of each collection ──
|
|
14083
|
+
async readIssue(id) {
|
|
14084
|
+
await this.require("work.read");
|
|
14085
|
+
const page = { limit: this.pageSize };
|
|
14086
|
+
const issue = await this.client.workIssue(id);
|
|
14087
|
+
const [occupancy, executions, attention, evidence, history, transcripts] = await Promise.all([
|
|
14088
|
+
this.client.workIssueReservation(id),
|
|
14089
|
+
this.client.workIssueExecutions(id, page),
|
|
14090
|
+
this.client.workIssueAttention(id, page),
|
|
14091
|
+
this.client.workIssueEvidence(id, page),
|
|
14092
|
+
this.client.workIssueActivity(id, page),
|
|
14093
|
+
this.client.workThreadTranscriptLinks(issue.threadId, page)
|
|
14094
|
+
]);
|
|
14095
|
+
return { issue, occupancy, executions, attention, evidence, history, transcripts };
|
|
14096
|
+
}
|
|
14097
|
+
async readPlan(id) {
|
|
14098
|
+
await this.require("work.read");
|
|
14099
|
+
const page = { limit: this.pageSize };
|
|
14100
|
+
const [plan, issues, executions, history] = await Promise.all([
|
|
14101
|
+
this.client.workPlan(id),
|
|
14102
|
+
this.client.workIssues({ ...page, planId: id }),
|
|
14103
|
+
this.client.workPlanExecutions(id, page),
|
|
14104
|
+
this.client.workPlanActivity(id, page)
|
|
14105
|
+
]);
|
|
14106
|
+
return { plan, issues, executions, history };
|
|
14107
|
+
}
|
|
14108
|
+
// ── Start, follow-up, inspect ──
|
|
14109
|
+
/**
|
|
14110
|
+
* Starts a canonical execution of an Issue (through its reservation) or a Plan. Never a legacy Build: under legacy
|
|
14111
|
+
* execution authority this refuses before writing. An occupied Issue answers with its owner.
|
|
14112
|
+
*/
|
|
14113
|
+
async start(scope, options = {}) {
|
|
14114
|
+
await this.require("execution.start");
|
|
14115
|
+
try {
|
|
14116
|
+
const result = await this.send({ type: "execution.start", target: scope, payload: {
|
|
14117
|
+
...options.contract ? { contract: options.contract } : {},
|
|
14118
|
+
...options.limits ? { limits: options.limits } : {},
|
|
14119
|
+
...options.activation ? { activation: options.activation } : {},
|
|
14120
|
+
...options.expectedScopeRevision ? { expectedScopeRevision: options.expectedScopeRevision } : {}
|
|
14121
|
+
} }, options.commandId);
|
|
14122
|
+
return { kind: "started", command: commandProgress(result), replayed: result.replayed, execution: execution(result) };
|
|
14123
|
+
} catch (error) {
|
|
14124
|
+
if (error instanceof ClientError && error.code === "issue_run_live")
|
|
14125
|
+
return { kind: "occupied", owner: error.details.owner ?? {}, reservation: error.details.reservation ?? null };
|
|
14126
|
+
throw error;
|
|
14127
|
+
}
|
|
14128
|
+
}
|
|
14129
|
+
/** The open execution that owns a scope now: the Issue's reservation owner, or the Plan's newest open execution. */
|
|
14130
|
+
async owner(scope) {
|
|
14131
|
+
if (scope.kind === "issue") {
|
|
14132
|
+
const occupancy = await this.client.workIssueReservation(scope.id);
|
|
14133
|
+
const owner = occupancy.reservation?.owner;
|
|
14134
|
+
if (!occupancy.occupied || !owner) throw new WorkFlowError("not_occupied", { issueId: scope.id });
|
|
14135
|
+
if (owner.kind !== "execution") throw new WorkFlowError("legacy_owner", { runId: owner.runId });
|
|
14136
|
+
return this.client.workExecution(owner.executionId);
|
|
14137
|
+
}
|
|
14138
|
+
let after = null;
|
|
14139
|
+
let open;
|
|
14140
|
+
do {
|
|
14141
|
+
const page = await this.client.workPlanExecutions(scope.id, { after, limit: 100 });
|
|
14142
|
+
open = page.items.filter((e) => e.closure === null).at(-1) ?? open;
|
|
14143
|
+
after = page.nextCursor;
|
|
14144
|
+
} while (after);
|
|
14145
|
+
if (!open) throw new WorkFlowError("not_occupied", { planId: scope.id });
|
|
14146
|
+
return open;
|
|
14147
|
+
}
|
|
14148
|
+
/** Durable input for the coordinator of the scope's (or the named) open execution, recorded in its inbox. */
|
|
14149
|
+
async followUp(target, body, commandId) {
|
|
14150
|
+
await this.require("execution.follow_up");
|
|
14151
|
+
const e = target.kind === "execution" ? await this.client.workExecution(target.id) : await this.owner(target);
|
|
14152
|
+
const result = await this.send({
|
|
14153
|
+
type: "execution.message",
|
|
14154
|
+
target: { kind: "execution", id: e.id },
|
|
14155
|
+
expectedGeneration: e.generation,
|
|
14156
|
+
payload: { body }
|
|
14157
|
+
}, commandId);
|
|
14158
|
+
return { command: commandProgress(result), replayed: result.replayed, executionId: e.id, threadId: e.threadId };
|
|
14159
|
+
}
|
|
14160
|
+
/** Progress, outcomes, controls, questions, messages, specialists and their transcripts: the first page of each. */
|
|
14161
|
+
async inspect(executionId) {
|
|
14162
|
+
await this.require("execution.inspect");
|
|
14163
|
+
const page = { limit: this.pageSize };
|
|
14164
|
+
const e = await this.client.workExecution(executionId);
|
|
14165
|
+
const [outcome, controls, questions, messages, assignments, packages, reports, children, history, transcripts, recoveries] = await Promise.all([
|
|
14166
|
+
this.client.workExecutionOutcome(e.id),
|
|
14167
|
+
this.client.workExecutionControls(e.id, page),
|
|
14168
|
+
this.client.workExecutionQuestions(e.id, page),
|
|
14169
|
+
this.client.workExecutionMessages(e.id, page),
|
|
14170
|
+
this.client.workExecutionAssignments(e.id, page),
|
|
14171
|
+
this.client.workExecutionPackages(e.id, page),
|
|
14172
|
+
this.client.workExecutionReports(e.id, page),
|
|
14173
|
+
this.client.workExecutionChildren(e.id, page),
|
|
14174
|
+
this.client.workExecutionActivity(e.id, page),
|
|
14175
|
+
this.client.workThreadTranscriptLinks(e.threadId, page),
|
|
14176
|
+
this.client.workExecutionRecoveries(e.id, page)
|
|
14177
|
+
]);
|
|
14178
|
+
const cancellation = await this.cancellation(e.id);
|
|
14179
|
+
return {
|
|
14180
|
+
execution: e,
|
|
14181
|
+
outcome,
|
|
14182
|
+
recoveries,
|
|
14183
|
+
cancellation,
|
|
14184
|
+
cancel: cancellation ? cancelProgress(e, cancellation) : null,
|
|
14185
|
+
controls: { items: controls.items.map(controlProgress), nextCursor: controls.nextCursor },
|
|
14186
|
+
questions,
|
|
14187
|
+
messages: { items: messages.items.map((m) => ({ ...m, progress: messageProgress(m) })), nextCursor: messages.nextCursor },
|
|
14188
|
+
assignments,
|
|
14189
|
+
packages,
|
|
14190
|
+
reports,
|
|
14191
|
+
children,
|
|
14192
|
+
history,
|
|
14193
|
+
transcripts
|
|
14194
|
+
};
|
|
14195
|
+
}
|
|
14196
|
+
/** The newest cancellation, or null when the execution was never cancelled (the read's own `not_found`). */
|
|
14197
|
+
async cancellation(executionId) {
|
|
14198
|
+
try {
|
|
14199
|
+
return await this.client.workExecutionCancellation(executionId);
|
|
14200
|
+
} catch (error) {
|
|
14201
|
+
if (error instanceof ClientError && error.code === "not_found") return null;
|
|
14202
|
+
throw error;
|
|
14203
|
+
}
|
|
14204
|
+
}
|
|
14205
|
+
// ── Controls: requested versus effective ──
|
|
14206
|
+
/**
|
|
14207
|
+
* Holds, releases, pauses, resumes or cancels an execution at the generation the caller observed (read now when not
|
|
14208
|
+
* given). The outcome is `requested` until the Hub records the control effective.
|
|
14209
|
+
*/
|
|
14210
|
+
async control(executionId, control, options = {}) {
|
|
14211
|
+
await this.require(`execution.${control}`);
|
|
14212
|
+
const generation = options.expectedGeneration ?? (await this.client.workExecution(executionId)).generation;
|
|
14213
|
+
const reason = options.reason ?? null;
|
|
14214
|
+
if (options.boundaryGraceMs !== void 0 && !(Number.isInteger(options.boundaryGraceMs) && options.boundaryGraceMs >= 0 && options.boundaryGraceMs <= maxClientCancelBoundaryGraceMs))
|
|
14215
|
+
throw new WorkFlowError("invalid_request", { field: "boundaryGraceMs", max: maxClientCancelBoundaryGraceMs });
|
|
14216
|
+
const payload = control === "cancel" ? { reason: reason ?? "Cancelled by a member", ...options.boundaryGraceMs !== void 0 ? { boundaryGraceMs: options.boundaryGraceMs } : {} } : { reason };
|
|
14217
|
+
const result = await this.send({
|
|
14218
|
+
type: `execution.${control}`,
|
|
14219
|
+
target: { kind: "execution", id: executionId },
|
|
14220
|
+
expectedGeneration: generation,
|
|
14221
|
+
payload
|
|
14222
|
+
}, options.commandId);
|
|
14223
|
+
const e = execution(result);
|
|
14224
|
+
const kind = control === "unhold" ? "unhold" : control;
|
|
14225
|
+
const requests = await this.client.workExecutionControls(e.id, { limit: this.pageSize });
|
|
14226
|
+
const request = requests.items.filter((r) => r.control === kind && r.commandId === result.commandId).at(-1);
|
|
14227
|
+
const outcome = {
|
|
14228
|
+
control,
|
|
14229
|
+
command: commandProgress(result),
|
|
14230
|
+
replayed: result.replayed,
|
|
14231
|
+
state: executionControlState(e, control),
|
|
14232
|
+
execution: e,
|
|
14233
|
+
request: request ? controlProgress(request) : null
|
|
14234
|
+
};
|
|
14235
|
+
if (control === "cancel") {
|
|
14236
|
+
const cancellation = await this.cancellation(e.id);
|
|
14237
|
+
outcome.cancellation = cancellation;
|
|
14238
|
+
outcome.cancel = cancelProgress(e, cancellation);
|
|
14239
|
+
}
|
|
14240
|
+
return outcome;
|
|
14241
|
+
}
|
|
14242
|
+
/** Publishes the next version of a specialist assignment (`expectedVersion` its current one). */
|
|
14243
|
+
async revise(assignmentId, changes, reason, options = {}) {
|
|
14244
|
+
await this.require("assignment.revise");
|
|
14245
|
+
const a = await this.client.workAssignment(assignmentId);
|
|
14246
|
+
const result = await this.send({
|
|
14247
|
+
type: "assignment.revise",
|
|
14248
|
+
target: { kind: "assignment", id: assignmentId },
|
|
14249
|
+
expectedGeneration: a.generation,
|
|
14250
|
+
expectedVersion: options.expectedVersion ?? a.assignmentVersion,
|
|
14251
|
+
payload: { changes, reason }
|
|
14252
|
+
}, options.commandId);
|
|
14253
|
+
return result;
|
|
14254
|
+
}
|
|
14255
|
+
// ── Recovery of interrupted work (DRI-325) ──
|
|
14256
|
+
/** Settles an effect of uncertain outcome by inspection, naming what was inspected. Never repeats the effect. */
|
|
14257
|
+
async reconcile(effectId, outcome, evidence, options) {
|
|
14258
|
+
await this.require("effect.reconcile");
|
|
14259
|
+
const result = await this.send({
|
|
14260
|
+
type: "effect.reconcile",
|
|
14261
|
+
target: { kind: "effect", id: effectId },
|
|
14262
|
+
expectedGeneration: options.expectedGeneration,
|
|
14263
|
+
payload: { outcome, evidence }
|
|
14264
|
+
}, options.commandId);
|
|
14265
|
+
return { command: commandProgress(result), replayed: result.replayed, execution: execution(result) };
|
|
14266
|
+
}
|
|
14267
|
+
/** Replaces an interrupted specialist Run of an open assignment with `runId`, once the old one is proven stopped. */
|
|
14268
|
+
async recover(assignmentId, input) {
|
|
14269
|
+
await this.require("assignment.recover");
|
|
14270
|
+
const a = await this.client.workAssignment(assignmentId);
|
|
14271
|
+
const { commandId, runId, ...rest } = input;
|
|
14272
|
+
const result = await this.send({
|
|
14273
|
+
type: "assignment.recover",
|
|
14274
|
+
target: { kind: "assignment", id: assignmentId },
|
|
14275
|
+
expectedGeneration: a.generation,
|
|
14276
|
+
payload: { runId: runId ?? crypto.randomUUID(), ...rest }
|
|
14277
|
+
}, commandId);
|
|
14278
|
+
return result;
|
|
14279
|
+
}
|
|
14280
|
+
/**
|
|
14281
|
+
* A member closes a cancelling execution as cancelled (or an escalated one as failed) once every Run is stopped,
|
|
14282
|
+
* acknowledging exactly the effects still outstanding (the cancellation's `blockers.effectIds`); the acknowledgement
|
|
14283
|
+
* waives their reconciliation, never the proof of stop. `effect.reconcile` settles one by inspection instead.
|
|
14284
|
+
*/
|
|
14285
|
+
async close(executionId, outcome, options = {}) {
|
|
14286
|
+
await this.require("execution.close");
|
|
14287
|
+
const generation = options.expectedGeneration ?? (await this.client.workExecution(executionId)).generation;
|
|
14288
|
+
const acknowledgement = options.acknowledge?.length ? { reason: options.reason ?? "Acknowledged by a member", effectIds: options.acknowledge } : null;
|
|
14289
|
+
const result = await this.send({
|
|
14290
|
+
type: "execution.close",
|
|
14291
|
+
target: { kind: "execution", id: executionId },
|
|
14292
|
+
expectedGeneration: generation,
|
|
14293
|
+
payload: { outcome, acknowledgement }
|
|
14294
|
+
}, options.commandId);
|
|
14295
|
+
const e = execution(result);
|
|
14296
|
+
const cancellation = outcome === "cancelled" ? await this.cancellation(e.id) : null;
|
|
14297
|
+
return {
|
|
14298
|
+
command: commandProgress(result),
|
|
14299
|
+
replayed: result.replayed,
|
|
14300
|
+
execution: e,
|
|
14301
|
+
...outcome === "cancelled" ? { cancel: cancelProgress(e, cancellation), cancellation } : {}
|
|
14302
|
+
};
|
|
14303
|
+
}
|
|
14304
|
+
// ── Questions and acceptance ──
|
|
14305
|
+
/** A member answers any question; a coordinator's credential answers a technical one addressed to it (version from its brief). */
|
|
14306
|
+
async answer(questionId, answer, options = {}) {
|
|
14307
|
+
await this.require("question.answer");
|
|
14308
|
+
const version2 = options.expectedVersion ?? (options.asRun ? (await this.client.workBrief()).questions?.find((q) => q.id === questionId)?.version : (await this.client.workQuestion(questionId)).version);
|
|
14309
|
+
if (version2 === void 0) throw new WorkFlowError("question_not_addressed", { questionId });
|
|
14310
|
+
const result = await this.send({
|
|
14311
|
+
type: "question.answer",
|
|
14312
|
+
target: { kind: "question", id: questionId },
|
|
14313
|
+
expectedVersion: version2,
|
|
14314
|
+
payload: { answer }
|
|
14315
|
+
}, options.commandId);
|
|
14316
|
+
if (result.outcome.kind !== "question") throw new WorkFlowError("unexpected_outcome", { kind: result.outcome.kind });
|
|
14317
|
+
return { command: commandProgress(result), replayed: result.replayed, question: result.outcome.question, delivery: result.outcome.question.answer?.delivery ?? null };
|
|
14318
|
+
}
|
|
14319
|
+
/** Forwards a question one level up: the addressed coordinator's own credential only (a member's is `forbidden`). */
|
|
14320
|
+
async forward(questionId, note, options = {}) {
|
|
14321
|
+
await this.require("question.forward");
|
|
14322
|
+
const version2 = options.expectedVersion ?? (await this.client.workBrief()).questions?.find((q) => q.id === questionId)?.version;
|
|
14323
|
+
if (version2 === void 0) throw new WorkFlowError("question_not_addressed", { questionId });
|
|
14324
|
+
return this.send({ type: "question.forward", target: { kind: "question", id: questionId }, expectedVersion: version2, payload: { note } }, options.commandId);
|
|
14325
|
+
}
|
|
14326
|
+
/** A member accepts or rejects a root execution's package (its contract names the authority). */
|
|
14327
|
+
async decide(executionId, packageId, decision, reason, options = {}) {
|
|
14328
|
+
await this.require("execution.decide");
|
|
14329
|
+
const generation = options.expectedGeneration ?? (await this.client.workExecution(executionId)).generation;
|
|
14330
|
+
const result = await this.send({
|
|
14331
|
+
type: "execution.decide",
|
|
14332
|
+
target: { kind: "execution", id: executionId },
|
|
14333
|
+
expectedGeneration: generation,
|
|
14334
|
+
payload: { packageId, decision, reason }
|
|
14335
|
+
}, options.commandId);
|
|
14336
|
+
return { command: commandProgress(result), replayed: result.replayed, execution: execution(result), outcome: await this.client.workExecutionOutcome(executionId) };
|
|
14337
|
+
}
|
|
14338
|
+
// ── A coordinated Run's own credential ──
|
|
14339
|
+
async brief() {
|
|
14340
|
+
await this.require("run.brief");
|
|
14341
|
+
return this.client.workBrief();
|
|
14342
|
+
}
|
|
14343
|
+
/**
|
|
14344
|
+
* The specialist acknowledges a revision once its own in-flight work is reconciled. `toolUseId` is the harness tool
|
|
14345
|
+
* call carrying this command (the Runner's PreToolUse context): the Hub excludes it from the in-flight check.
|
|
14346
|
+
*/
|
|
14347
|
+
async incorporate(assignmentId, version2, options) {
|
|
14348
|
+
await this.require("assignment.incorporate");
|
|
14349
|
+
return this.send({
|
|
14350
|
+
type: "assignment.incorporate",
|
|
14351
|
+
target: { kind: "assignment", id: assignmentId },
|
|
14352
|
+
expectedGeneration: options.generation,
|
|
14353
|
+
payload: { version: version2, ...options.toolUseId ? { toolUseId: options.toolUseId } : {} }
|
|
14354
|
+
}, options.commandId);
|
|
14355
|
+
}
|
|
14356
|
+
};
|
|
14357
|
+
|
|
13839
14358
|
// ../../packages/client/src/index.ts
|
|
14359
|
+
function discussionQuery(page) {
|
|
14360
|
+
const query = new URLSearchParams(workQuery(page).slice(1));
|
|
14361
|
+
if (page.parentId) query.set("parentId", page.parentId);
|
|
14362
|
+
if (page.commentId) query.set("commentId", page.commentId);
|
|
14363
|
+
const text = query.toString();
|
|
14364
|
+
return text ? `?${text}` : "";
|
|
14365
|
+
}
|
|
14366
|
+
function relationQuery(page) {
|
|
14367
|
+
const query = new URLSearchParams(workQuery(page).slice(1));
|
|
14368
|
+
if (page.kind) query.set("kind", page.kind);
|
|
14369
|
+
if (page.state) query.set("state", page.state);
|
|
14370
|
+
const text = query.toString();
|
|
14371
|
+
return text ? `?${text}` : "";
|
|
14372
|
+
}
|
|
14373
|
+
function workQuery(page = {}) {
|
|
14374
|
+
const query = new URLSearchParams();
|
|
14375
|
+
if (page.after) query.set("after", page.after);
|
|
14376
|
+
if (page.limit !== void 0) query.set("limit", String(page.limit));
|
|
14377
|
+
if (page.planId) query.set("planId", page.planId);
|
|
14378
|
+
if (page.lifecycle) query.set("lifecycle", page.lifecycle);
|
|
14379
|
+
const text = query.toString();
|
|
14380
|
+
return text ? `?${text}` : "";
|
|
14381
|
+
}
|
|
14382
|
+
function sourceQuery(page) {
|
|
14383
|
+
const query = new URLSearchParams();
|
|
14384
|
+
for (const [key, value] of Object.entries(page))
|
|
14385
|
+
if (value !== void 0 && value !== null && value !== "") query.set(key, String(value));
|
|
14386
|
+
const text = query.toString();
|
|
14387
|
+
return text ? `?${text}` : "";
|
|
14388
|
+
}
|
|
13840
14389
|
var runArtifactSchema = external_exports.object({ name: external_exports.string(), seq: external_exports.number().int(), json: external_exports.json() });
|
|
13841
14390
|
var githubRepositorySchema = external_exports.object({
|
|
13842
14391
|
installationId: external_exports.number().int().positive(),
|
|
@@ -13846,13 +14395,15 @@ var githubRepositorySchema = external_exports.object({
|
|
|
13846
14395
|
defaultBranch: external_exports.string()
|
|
13847
14396
|
});
|
|
13848
14397
|
var ClientError = class extends Error {
|
|
13849
|
-
constructor(code, status) {
|
|
14398
|
+
constructor(code, status, details = {}) {
|
|
13850
14399
|
super(code);
|
|
13851
14400
|
this.code = code;
|
|
13852
14401
|
this.status = status;
|
|
14402
|
+
this.details = details;
|
|
13853
14403
|
}
|
|
13854
14404
|
code;
|
|
13855
14405
|
status;
|
|
14406
|
+
details;
|
|
13856
14407
|
};
|
|
13857
14408
|
var DrispClient = class _DrispClient {
|
|
13858
14409
|
constructor(baseUrl, orgId, token) {
|
|
@@ -13877,21 +14428,25 @@ var DrispClient = class _DrispClient {
|
|
|
13877
14428
|
if (!match) throw new Error("invalid_hub_url");
|
|
13878
14429
|
return new _DrispClient(url.origin, decodeURIComponent(match[1]), async () => token);
|
|
13879
14430
|
}
|
|
13880
|
-
async request(
|
|
14431
|
+
async request(path17, body) {
|
|
13881
14432
|
const response = await fetch(
|
|
13882
|
-
`${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}${
|
|
14433
|
+
`${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}${path17}`,
|
|
13883
14434
|
{
|
|
13884
14435
|
method: body === void 0 ? "GET" : "POST",
|
|
13885
14436
|
headers: {
|
|
13886
14437
|
Authorization: `Bearer ${await this.token()}`,
|
|
13887
|
-
"Content-Type": "application/json"
|
|
14438
|
+
"Content-Type": "application/json",
|
|
14439
|
+
// DRI-342: the client contract this build speaks; a Hub refuses one below its minimum (`client_upgrade_required`).
|
|
14440
|
+
[workClientContractHeader]: String(workClientContract)
|
|
13888
14441
|
},
|
|
13889
14442
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
13890
14443
|
}
|
|
13891
14444
|
);
|
|
13892
14445
|
const result = await response.json();
|
|
13893
|
-
if (!response.ok)
|
|
13894
|
-
|
|
14446
|
+
if (!response.ok) {
|
|
14447
|
+
const { error, ...details } = result;
|
|
14448
|
+
throw new ClientError(error ?? `HTTP ${response.status}`, response.status, details);
|
|
14449
|
+
}
|
|
13895
14450
|
return result;
|
|
13896
14451
|
}
|
|
13897
14452
|
submit(command) {
|
|
@@ -13915,6 +14470,668 @@ var DrispClient = class _DrispClient {
|
|
|
13915
14470
|
startIssue(id, command) {
|
|
13916
14471
|
return this.request(`/tracker/issues/${encodeURIComponent(id)}/start`, commandSchema.parse(command));
|
|
13917
14472
|
}
|
|
14473
|
+
// ── Attention requests and execution policy (DRI-300) ──
|
|
14474
|
+
/**
|
|
14475
|
+
* Sends one attention or execution-policy command (`attention.request`, `attention.resolve`, `attention.sync_run`,
|
|
14476
|
+
* `execution_policy.hold`, `execution_policy.resume`, `execution_policy.set_agent_eligibility`) on the Work
|
|
14477
|
+
* command route, with its receipts and replay. Resolving attention never answers a Run: answer through `submit`.
|
|
14478
|
+
*/
|
|
14479
|
+
async workAttentionCommand(command) {
|
|
14480
|
+
return workAttentionCommandResultSchema.parse(await this.request("/work/commands", workAttentionCommandSchema.parse(command)));
|
|
14481
|
+
}
|
|
14482
|
+
/** An Issue's attention requests, oldest first; `state` narrows to open (its Needs Human) or resolved ones. */
|
|
14483
|
+
async workIssueAttention(id, page = {}) {
|
|
14484
|
+
const query = workQuery(page);
|
|
14485
|
+
const state = page.state ? `${query ? "&" : "?"}state=${page.state}` : "";
|
|
14486
|
+
return workAttentionRequestPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/attention${query}${state}`));
|
|
14487
|
+
}
|
|
14488
|
+
async workAttentionRequest(id) {
|
|
14489
|
+
return workAttentionRequestSchema.parse(await this.request(`/work/attention-requests/${encodeURIComponent(id)}`));
|
|
14490
|
+
}
|
|
14491
|
+
async workAttentionActivity(id, page = {}) {
|
|
14492
|
+
return workAttentionActivityPageSchema.parse(
|
|
14493
|
+
await this.request(`/work/attention-requests/${encodeURIComponent(id)}/activity${workQuery(page)}`)
|
|
14494
|
+
);
|
|
14495
|
+
}
|
|
14496
|
+
async workExecutionPolicy(issueId) {
|
|
14497
|
+
return workExecutionPolicySchema.parse(await this.request(`/work/issues/${encodeURIComponent(issueId)}/execution-policy`));
|
|
14498
|
+
}
|
|
14499
|
+
async workExecutionPolicyActivity(issueId, page = {}) {
|
|
14500
|
+
return workAttentionActivityPageSchema.parse(
|
|
14501
|
+
await this.request(`/work/issues/${encodeURIComponent(issueId)}/execution-policy/activity${workQuery(page)}`)
|
|
14502
|
+
);
|
|
14503
|
+
}
|
|
14504
|
+
// ── end DRI-300 ──
|
|
14505
|
+
/**
|
|
14506
|
+
* Sends one versioned Team command to the Work core (DRI-294). The caller retains `commandId` for retries: an
|
|
14507
|
+
* identical retry returns the original result with `replayed: true`; a changed payload is `idempotency_conflict`.
|
|
14508
|
+
*/
|
|
14509
|
+
async workCommand(command) {
|
|
14510
|
+
return workCommandResultSchema.parse(await this.request("/work/commands", workCommandSchema.parse(command)));
|
|
14511
|
+
}
|
|
14512
|
+
async workIssue(id) {
|
|
14513
|
+
return workIssueSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}`));
|
|
14514
|
+
}
|
|
14515
|
+
/** Issues by number: active only unless `lifecycle` asks for `archived`, `deleted` or `all` (DRI-304). */
|
|
14516
|
+
async workIssues(page = {}) {
|
|
14517
|
+
return workIssuePageSchema.parse(await this.request(`/work/issues${workQuery(page)}`));
|
|
14518
|
+
}
|
|
14519
|
+
async workIssueActivity(id, page = {}) {
|
|
14520
|
+
return workActivityPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/activity${workQuery(page)}`));
|
|
14521
|
+
}
|
|
14522
|
+
async workIssueScopeRevisions(id, page = {}) {
|
|
14523
|
+
return workScopeRevisionPageSchema.parse(
|
|
14524
|
+
await this.request(`/work/issues/${encodeURIComponent(id)}/scope-revisions${workQuery(page)}`)
|
|
14525
|
+
);
|
|
14526
|
+
}
|
|
14527
|
+
async workPlan(id) {
|
|
14528
|
+
return workPlanSchema.parse(await this.request(`/work/plans/${encodeURIComponent(id)}`));
|
|
14529
|
+
}
|
|
14530
|
+
/** Plans by number (DRI-296): active only unless `lifecycle` asks for `archived`, `deleted` or `all` (DRI-304). */
|
|
14531
|
+
async workPlans(page = {}) {
|
|
14532
|
+
return workPlanPageSchema.parse(await this.request(`/work/plans${workQuery(page)}`));
|
|
14533
|
+
}
|
|
14534
|
+
async workPlanActivity(id, page = {}) {
|
|
14535
|
+
return workActivityPageSchema.parse(await this.request(`/work/plans/${encodeURIComponent(id)}/activity${workQuery(page)}`));
|
|
14536
|
+
}
|
|
14537
|
+
// ── Sub-Issue trees (DRI-296); `plan.archive`, `plan.unarchive`, `issue.set_plan`, `issue.set_parent` go through `workCommand` ──
|
|
14538
|
+
/** An Issue's direct children, by number. */
|
|
14539
|
+
async workIssueChildren(id, page = {}) {
|
|
14540
|
+
return workIssuePageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/children${workQuery(page)}`));
|
|
14541
|
+
}
|
|
14542
|
+
/** An Issue's ancestors, nearest first; each page continues from the last ancestor returned. */
|
|
14543
|
+
async workIssueAncestors(id, page = {}) {
|
|
14544
|
+
return workIssuePageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/ancestors${workQuery(page)}`));
|
|
14545
|
+
}
|
|
14546
|
+
// ── end DRI-296 ──
|
|
14547
|
+
// ── Lifecycle (DRI-304); `issue.archive`, `issue.delete`, `issue.restore`, `plan.delete`, `plan.restore` go through `workCommand` ──
|
|
14548
|
+
/** An Issue's lifecycle events (completion, cancellation, reopening, archival, tombstone, restoration), oldest first. */
|
|
14549
|
+
async workIssueLifecycle(id, page = {}) {
|
|
14550
|
+
return workLifecycleEventPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/lifecycle${workQuery(page)}`));
|
|
14551
|
+
}
|
|
14552
|
+
async workPlanLifecycle(id, page = {}) {
|
|
14553
|
+
return workLifecycleEventPageSchema.parse(await this.request(`/work/plans/${encodeURIComponent(id)}/lifecycle${workQuery(page)}`));
|
|
14554
|
+
}
|
|
14555
|
+
/** Who holds a complete source key and whether that holder is tombstoned: the contract imports consume. */
|
|
14556
|
+
async workSourceKeyLifecycle(key) {
|
|
14557
|
+
return workSourceKeyLifecycleSchema.parse(await this.request(`/work/source-references/lifecycle?${new URLSearchParams(key)}`));
|
|
14558
|
+
}
|
|
14559
|
+
// ── end DRI-304 ──
|
|
14560
|
+
/**
|
|
14561
|
+
* Sends one delivery workflow command (`workflow.create`, `workflow.publish`, `workflow.activate`; DRI-297). Team
|
|
14562
|
+
* admin authority; the same envelope, receipts and replay as `workCommand`.
|
|
14563
|
+
*/
|
|
14564
|
+
async workflowCommand(command) {
|
|
14565
|
+
return workCommandResultSchema.parse(await this.request("/work/delivery-workflows/commands", workCommandSchema.parse(command)));
|
|
14566
|
+
}
|
|
14567
|
+
async deliveryWorkflows(page = {}) {
|
|
14568
|
+
return workDeliveryWorkflowPageSchema.parse(await this.request(`/work/delivery-workflows${workQuery(page)}`));
|
|
14569
|
+
}
|
|
14570
|
+
/** One delivery workflow; `active` names the Team's active one. */
|
|
14571
|
+
async deliveryWorkflow(id = "active") {
|
|
14572
|
+
return workDeliveryWorkflowSchema.parse(await this.request(`/work/delivery-workflows/${encodeURIComponent(id)}`));
|
|
14573
|
+
}
|
|
14574
|
+
async deliveryWorkflowRevision(id, revision) {
|
|
14575
|
+
return workDeliveryWorkflowRevisionSchema.parse(
|
|
14576
|
+
await this.request(`/work/delivery-workflows/${encodeURIComponent(id)}/revisions/${revision}`)
|
|
14577
|
+
);
|
|
14578
|
+
}
|
|
14579
|
+
async deliveryWorkflowRevisions(id, page = {}) {
|
|
14580
|
+
return workDeliveryWorkflowRevisionPageSchema.parse(
|
|
14581
|
+
await this.request(`/work/delivery-workflows/${encodeURIComponent(id)}/revisions${workQuery(page)}`)
|
|
14582
|
+
);
|
|
14583
|
+
}
|
|
14584
|
+
async deliveryMigrations(page = {}) {
|
|
14585
|
+
return workDeliveryMigrationPageSchema.parse(await this.request(`/work/delivery-migrations${workQuery(page)}`));
|
|
14586
|
+
}
|
|
14587
|
+
/** The moves out of an Issue's current status, each evaluated for the caller by the Hub's gate evaluator. */
|
|
14588
|
+
async workIssueTransitions(id) {
|
|
14589
|
+
return workIssueTransitionsSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/transitions`));
|
|
14590
|
+
}
|
|
14591
|
+
// ── Source identities and attribution (DRI-302) ──
|
|
14592
|
+
/**
|
|
14593
|
+
* Sends one source command (`source_space.register`, `source_principal.record`, `source_principal.map`,
|
|
14594
|
+
* `source_reference.record`, `observe`, `associate`, `alias_key`) on the Work command route, with its receipts.
|
|
14595
|
+
*/
|
|
14596
|
+
async workSourceCommand(command) {
|
|
14597
|
+
return workSourceCommandResultSchema.parse(await this.request("/work/commands", workSourceCommandSchema.parse(command)));
|
|
14598
|
+
}
|
|
14599
|
+
async workSourceSpaces(page = {}) {
|
|
14600
|
+
return workSourceSpacePageSchema.parse(await this.request(`/work/source-spaces${workQuery(page)}`));
|
|
14601
|
+
}
|
|
14602
|
+
async workSourceSpace(id) {
|
|
14603
|
+
return workSourceSpaceSchema.parse(await this.request(`/work/source-spaces/${encodeURIComponent(id)}`));
|
|
14604
|
+
}
|
|
14605
|
+
async workSourcePrincipals(page = {}) {
|
|
14606
|
+
return workSourcePrincipalPageSchema.parse(await this.request(`/work/source-principals${sourceQuery(page)}`));
|
|
14607
|
+
}
|
|
14608
|
+
async workSourcePrincipal(id) {
|
|
14609
|
+
return workSourcePrincipalSchema.parse(await this.request(`/work/source-principals/${encodeURIComponent(id)}`));
|
|
14610
|
+
}
|
|
14611
|
+
/** References, optionally of one space or carrying a readable identifier now or formerly (an alias, not identity). */
|
|
14612
|
+
async workSourceReferences(page = {}) {
|
|
14613
|
+
return workSourceReferencePageSchema.parse(await this.request(`/work/source-references${sourceQuery(page)}`));
|
|
14614
|
+
}
|
|
14615
|
+
async workSourceReference(id) {
|
|
14616
|
+
return workSourceReferenceSchema.parse(await this.request(`/work/source-references/${encodeURIComponent(id)}`));
|
|
14617
|
+
}
|
|
14618
|
+
/** The reference holding a complete source key (canonical or alias) in this Team; `not_found` otherwise. */
|
|
14619
|
+
async workResolveSource(key) {
|
|
14620
|
+
return workSourceReferenceSchema.parse(await this.request(`/work/source-references/resolve?${new URLSearchParams(key)}`));
|
|
14621
|
+
}
|
|
14622
|
+
async workSourceAliases(id, page = {}) {
|
|
14623
|
+
return workSourceAliasPageSchema.parse(await this.request(`/work/source-references/${encodeURIComponent(id)}/aliases${workQuery(page)}`));
|
|
14624
|
+
}
|
|
14625
|
+
async workSourceAssociations(id, page = {}) {
|
|
14626
|
+
return workSourceAssociationPageSchema.parse(
|
|
14627
|
+
await this.request(`/work/source-references/${encodeURIComponent(id)}/associations${workQuery(page)}`)
|
|
14628
|
+
);
|
|
14629
|
+
}
|
|
14630
|
+
async workSourceActivity(subject, page = {}) {
|
|
14631
|
+
const collection = { source_space: "source-spaces", source_principal: "source-principals", source_reference: "source-references" }[subject.kind];
|
|
14632
|
+
return workSourceActivityPageSchema.parse(
|
|
14633
|
+
await this.request(`/work/${collection}/${encodeURIComponent(subject.id)}/activity${workQuery(page)}`)
|
|
14634
|
+
);
|
|
14635
|
+
}
|
|
14636
|
+
/** The source references associated with an Issue or Plan now. */
|
|
14637
|
+
async workOwnerSourceReferences(owner, page = {}) {
|
|
14638
|
+
return workSourceReferencePageSchema.parse(
|
|
14639
|
+
await this.request(`/work/${owner.kind}s/${encodeURIComponent(owner.id)}/source-references${workQuery(page)}`)
|
|
14640
|
+
);
|
|
14641
|
+
}
|
|
14642
|
+
/** Every association change to or from an Issue or Plan, including references since moved elsewhere. */
|
|
14643
|
+
async workOwnerSourceHistory(owner, page = {}) {
|
|
14644
|
+
return workSourceAssociationPageSchema.parse(
|
|
14645
|
+
await this.request(`/work/${owner.kind}s/${encodeURIComponent(owner.id)}/source-history${workQuery(page)}`)
|
|
14646
|
+
);
|
|
14647
|
+
}
|
|
14648
|
+
// ── end DRI-302 ──
|
|
14649
|
+
// ── Assignees and Team labels (DRI-295) ──
|
|
14650
|
+
/**
|
|
14651
|
+
* Sends one membership or label command (`issue_membership.assign`, `issue_membership.unassign`,
|
|
14652
|
+
* `issue_membership.add_label`, `issue_membership.remove_label` at the Issue's membership version; `label.create`,
|
|
14653
|
+
* `label.edit`) on the Work command route, with its receipts and replay.
|
|
14654
|
+
*/
|
|
14655
|
+
async workMembershipCommand(command) {
|
|
14656
|
+
return workMembershipCommandResultSchema.parse(await this.request("/work/commands", workMembershipCommandSchema.parse(command)));
|
|
14657
|
+
}
|
|
14658
|
+
/** The Issue's membership version (what a membership command names) and its current counts. */
|
|
14659
|
+
async workIssueMembership(id) {
|
|
14660
|
+
return workIssueMembershipSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/membership`));
|
|
14661
|
+
}
|
|
14662
|
+
async workIssueMembershipActivity(id, page = {}) {
|
|
14663
|
+
return workMembershipActivityPageSchema.parse(
|
|
14664
|
+
await this.request(`/work/issues/${encodeURIComponent(id)}/membership/activity${workQuery(page)}`)
|
|
14665
|
+
);
|
|
14666
|
+
}
|
|
14667
|
+
/** An Issue's current assignees, oldest first; `state: 'all'` includes removed periods. */
|
|
14668
|
+
async workIssueAssignees(id, page = {}) {
|
|
14669
|
+
const query = workQuery(page);
|
|
14670
|
+
const state = page.state ? `${query ? "&" : "?"}state=${page.state}` : "";
|
|
14671
|
+
return workIssueAssigneePageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/assignees${query}${state}`));
|
|
14672
|
+
}
|
|
14673
|
+
/** An Issue's label memberships, each with the label's current record; `state: 'all'` includes removed ones. */
|
|
14674
|
+
async workIssueLabels(id, page = {}) {
|
|
14675
|
+
const query = workQuery(page);
|
|
14676
|
+
const state = page.state ? `${query ? "&" : "?"}state=${page.state}` : "";
|
|
14677
|
+
return workIssueLabelPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/labels${query}${state}`));
|
|
14678
|
+
}
|
|
14679
|
+
async workLabels(page = {}) {
|
|
14680
|
+
return workLabelPageSchema.parse(await this.request(`/work/labels${workQuery(page)}`));
|
|
14681
|
+
}
|
|
14682
|
+
async workLabel(id) {
|
|
14683
|
+
return workLabelSchema.parse(await this.request(`/work/labels/${encodeURIComponent(id)}`));
|
|
14684
|
+
}
|
|
14685
|
+
/** Issues currently carrying the label, by number. */
|
|
14686
|
+
async workLabelIssues(id, page = {}) {
|
|
14687
|
+
return workLabelIssuePageSchema.parse(await this.request(`/work/labels/${encodeURIComponent(id)}/issues${workQuery(page)}`));
|
|
14688
|
+
}
|
|
14689
|
+
async workLabelActivity(id, page = {}) {
|
|
14690
|
+
return workMembershipActivityPageSchema.parse(
|
|
14691
|
+
await this.request(`/work/labels/${encodeURIComponent(id)}/activity${workQuery(page)}`)
|
|
14692
|
+
);
|
|
14693
|
+
}
|
|
14694
|
+
// ── end DRI-295 ──
|
|
14695
|
+
// ── Delivery candidates and evidence (DRI-298) ──
|
|
14696
|
+
/**
|
|
14697
|
+
* Sends one candidate or evidence command (`candidate.propose`, `candidate.select` at the selection's version,
|
|
14698
|
+
* `evidence.record`, `evidence.retract` and `evidence.accept` at the evidence's version) on the Work command route,
|
|
14699
|
+
* with its receipts and replay. Recorded evidence may advance the Issue automatically; the result says how.
|
|
14700
|
+
*/
|
|
14701
|
+
async workEvidenceCommand(command) {
|
|
14702
|
+
return workEvidenceCommandResultSchema.parse(await this.request("/work/commands", workEvidenceCommandSchema.parse(command)));
|
|
14703
|
+
}
|
|
14704
|
+
async workIssueCandidates(id, page = {}) {
|
|
14705
|
+
return workCandidatePageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/candidates${workQuery(page)}`));
|
|
14706
|
+
}
|
|
14707
|
+
/** The Issue's current-candidate selection and its version. */
|
|
14708
|
+
async workIssueCandidate(id) {
|
|
14709
|
+
return workCandidateSelectionSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/candidate`));
|
|
14710
|
+
}
|
|
14711
|
+
async workIssueCandidateActivity(id, page = {}) {
|
|
14712
|
+
return workEvidenceActivityPageSchema.parse(
|
|
14713
|
+
await this.request(`/work/issues/${encodeURIComponent(id)}/candidate/activity${workQuery(page)}`)
|
|
14714
|
+
);
|
|
14715
|
+
}
|
|
14716
|
+
/** An Issue's evidence, oldest first, each with its applicability now; narrow by candidate or kind. */
|
|
14717
|
+
async workIssueEvidence(id, page = {}) {
|
|
14718
|
+
const query = new URLSearchParams(workQuery(page).slice(1));
|
|
14719
|
+
if (page.candidateId) query.set("candidateId", page.candidateId);
|
|
14720
|
+
if (page.kind) query.set("kind", page.kind);
|
|
14721
|
+
const text = query.toString();
|
|
14722
|
+
return workEvidencePageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/evidence${text ? `?${text}` : ""}`));
|
|
14723
|
+
}
|
|
14724
|
+
async workCandidate(id) {
|
|
14725
|
+
return workCandidateSchema.parse(await this.request(`/work/candidates/${encodeURIComponent(id)}`));
|
|
14726
|
+
}
|
|
14727
|
+
async workCandidateEvidence(id, page = {}) {
|
|
14728
|
+
return workEvidencePageSchema.parse(await this.request(`/work/candidates/${encodeURIComponent(id)}/evidence${workQuery(page)}`));
|
|
14729
|
+
}
|
|
14730
|
+
async workEvidence(id) {
|
|
14731
|
+
return workEvidenceSchema.parse(await this.request(`/work/evidence/${encodeURIComponent(id)}`));
|
|
14732
|
+
}
|
|
14733
|
+
/** A candidate's or an evidence record's history, oldest first. */
|
|
14734
|
+
async workEvidenceActivity(subject, page = {}) {
|
|
14735
|
+
const collection = subject.kind === "candidate" ? "candidates" : "evidence";
|
|
14736
|
+
return workEvidenceActivityPageSchema.parse(
|
|
14737
|
+
await this.request(`/work/${collection}/${encodeURIComponent(subject.id)}/activity${workQuery(page)}`)
|
|
14738
|
+
);
|
|
14739
|
+
}
|
|
14740
|
+
// ── end DRI-298 ──
|
|
14741
|
+
// ── Deployments, verification attempts and production confirmation (DRI-299) ──
|
|
14742
|
+
/**
|
|
14743
|
+
* Sends one deployment or verification command (`deployment.record`, `deployment.retire` at the deployment's
|
|
14744
|
+
* version, `verification.start`) on the Work command route. QA results and production confirmation are evidence
|
|
14745
|
+
* commands (`workEvidenceCommand` with `evidence.record` naming the attempt, or `production.confirm`).
|
|
14746
|
+
*/
|
|
14747
|
+
async workDeploymentCommand(command) {
|
|
14748
|
+
return workDeploymentCommandResultSchema.parse(await this.request("/work/commands", workDeploymentCommandSchema.parse(command)));
|
|
14749
|
+
}
|
|
14750
|
+
/** An Issue's deployments in recording order; narrow by candidate, environment or lifecycle. */
|
|
14751
|
+
async workIssueDeployments(id, page = {}) {
|
|
14752
|
+
const query = new URLSearchParams(workQuery(page).slice(1));
|
|
14753
|
+
for (const key of ["candidateId", "environment", "lifecycle"]) if (page[key]) query.set(key, page[key]);
|
|
14754
|
+
const text = query.toString();
|
|
14755
|
+
return workDeploymentPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/deployments${text ? `?${text}` : ""}`));
|
|
14756
|
+
}
|
|
14757
|
+
/** An Issue's verification attempts in start order, each with its outcome and standing now. */
|
|
14758
|
+
async workIssueVerificationAttempts(id, page = {}) {
|
|
14759
|
+
const query = new URLSearchParams(workQuery(page).slice(1));
|
|
14760
|
+
if (page.deploymentId) query.set("deploymentId", page.deploymentId);
|
|
14761
|
+
const text = query.toString();
|
|
14762
|
+
return workVerificationAttemptPageSchema.parse(
|
|
14763
|
+
await this.request(`/work/issues/${encodeURIComponent(id)}/verification-attempts${text ? `?${text}` : ""}`)
|
|
14764
|
+
);
|
|
14765
|
+
}
|
|
14766
|
+
/** An Issue's staging-to-production trail: its production confirmations with the records they name. */
|
|
14767
|
+
async workIssueProductionTrail(id, page = {}) {
|
|
14768
|
+
return workProductionTrailPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/production-trail${workQuery(page)}`));
|
|
14769
|
+
}
|
|
14770
|
+
async workDeployment(id) {
|
|
14771
|
+
return workDeploymentSchema.parse(await this.request(`/work/deployments/${encodeURIComponent(id)}`));
|
|
14772
|
+
}
|
|
14773
|
+
async workDeploymentAttempts(id, page = {}) {
|
|
14774
|
+
return workVerificationAttemptPageSchema.parse(await this.request(`/work/deployments/${encodeURIComponent(id)}/attempts${workQuery(page)}`));
|
|
14775
|
+
}
|
|
14776
|
+
async workVerificationAttempt(id) {
|
|
14777
|
+
return workVerificationAttemptSchema.parse(await this.request(`/work/verification-attempts/${encodeURIComponent(id)}`));
|
|
14778
|
+
}
|
|
14779
|
+
/** A deployment's or a verification attempt's history, oldest first. */
|
|
14780
|
+
async workDeploymentActivity(subject, page = {}) {
|
|
14781
|
+
const collection = subject.kind === "deployment" ? "deployments" : "verification-attempts";
|
|
14782
|
+
return workDeploymentActivityPageSchema.parse(
|
|
14783
|
+
await this.request(`/work/${collection}/${encodeURIComponent(subject.id)}/activity${workQuery(page)}`)
|
|
14784
|
+
);
|
|
14785
|
+
}
|
|
14786
|
+
// ── end DRI-299 ──
|
|
14787
|
+
// ── Dependencies, duplicates and related Issues (DRI-301) ──
|
|
14788
|
+
/** Sends one relation command (`issue_relation.add`, `issue_relation.remove`) on the Work command route. */
|
|
14789
|
+
async workRelationCommand(command) {
|
|
14790
|
+
return workRelationCommandResultSchema.parse(await this.request("/work/commands", workRelationCommandSchema.parse(command)));
|
|
14791
|
+
}
|
|
14792
|
+
/**
|
|
14793
|
+
* An Issue's own relations, oldest first: its prerequisites, its canonical Issue and its related links (either side);
|
|
14794
|
+
* `kind` narrows to one, `state: 'all'` includes removed periods.
|
|
14795
|
+
*/
|
|
14796
|
+
async workIssueRelations(id, page = {}) {
|
|
14797
|
+
return workIssueRelationPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/relations${relationQuery(page)}`));
|
|
14798
|
+
}
|
|
14799
|
+
/** The Issues blocked by this one (`dependents`) or marked duplicates of it (`duplicates`), oldest first. */
|
|
14800
|
+
async workIssueInverseRelations(id, kind, page = {}) {
|
|
14801
|
+
return workIssueRelationPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/${kind}${relationQuery(page)}`));
|
|
14802
|
+
}
|
|
14803
|
+
/** Whether every prerequisite of the Issue is satisfied now, and a page of the unsatisfied ones. */
|
|
14804
|
+
async workDependencyGate(id, page = {}) {
|
|
14805
|
+
return workDependencyGateSchema.parse(await this.request(`/work/issues/${encodeURIComponent(id)}/dependency-gate${workQuery(page)}`));
|
|
14806
|
+
}
|
|
14807
|
+
async workRelation(id) {
|
|
14808
|
+
return workIssueRelationSchema.parse(await this.request(`/work/relations/${encodeURIComponent(id)}`));
|
|
14809
|
+
}
|
|
14810
|
+
async workRelationActivity(id, page = {}) {
|
|
14811
|
+
return workRelationActivityPageSchema.parse(await this.request(`/work/relations/${encodeURIComponent(id)}/activity${workQuery(page)}`));
|
|
14812
|
+
}
|
|
14813
|
+
// ── end DRI-301 ──
|
|
14814
|
+
// ── Pending imported relations (DRI-308) ──
|
|
14815
|
+
/** Sends `pending_relation.dismiss` or `pending_relation.resolve` on the Work command route. */
|
|
14816
|
+
async workPendingRelationCommand(command) {
|
|
14817
|
+
return workPendingRelationCommandResultSchema.parse(await this.request("/work/commands", workPendingRelationCommandSchema.parse(command)));
|
|
14818
|
+
}
|
|
14819
|
+
/** The pending imported relations naming an Issue (`issue`) or recorded by an import batch (`import`), oldest first. */
|
|
14820
|
+
async workPendingRelations(of, page = {}) {
|
|
14821
|
+
const query = new URLSearchParams(workQuery(page).slice(1));
|
|
14822
|
+
if (page.disposition) query.set("disposition", page.disposition);
|
|
14823
|
+
const text = query.toString();
|
|
14824
|
+
return workPendingRelationPageSchema.parse(await this.request(
|
|
14825
|
+
`/work/${of.kind === "issue" ? "issues" : "imports"}/${encodeURIComponent(of.id)}/pending-relations${text ? `?${text}` : ""}`
|
|
14826
|
+
));
|
|
14827
|
+
}
|
|
14828
|
+
async workPendingRelation(id) {
|
|
14829
|
+
return workPendingRelationSchema.parse(await this.request(`/work/pending-relations/${encodeURIComponent(id)}`));
|
|
14830
|
+
}
|
|
14831
|
+
async workPendingRelationActivity(id, page = {}) {
|
|
14832
|
+
return workPendingRelationActivityPageSchema.parse(await this.request(`/work/pending-relations/${encodeURIComponent(id)}/activity${workQuery(page)}`));
|
|
14833
|
+
}
|
|
14834
|
+
// ── end DRI-308 ──
|
|
14835
|
+
// ── Execution reservations and canonical admission (DRI-310) ──
|
|
14836
|
+
/**
|
|
14837
|
+
* Sends one reservation command (`legacy_alias.record`, Team admin) on the Work command route, with its receipts
|
|
14838
|
+
* and replay. DRI-314 retired the provisional lifecycle commands: Coordination's own (`coordinationCommand`) start,
|
|
14839
|
+
* bind and close executions.
|
|
14840
|
+
*/
|
|
14841
|
+
async workReservationCommand(command) {
|
|
14842
|
+
return workReservationCommandResultSchema.parse(await this.request("/work/commands", workReservationCommandSchema.parse(command)));
|
|
14843
|
+
}
|
|
14844
|
+
/** An Issue's occupancy: its ownership generation and held reservation, if any. */
|
|
14845
|
+
async workIssueReservation(issueId) {
|
|
14846
|
+
return workIssueReservationSchema.parse(await this.request(`/work/issues/${encodeURIComponent(issueId)}/reservation`));
|
|
14847
|
+
}
|
|
14848
|
+
/** Every reservation an Issue has had, oldest first. */
|
|
14849
|
+
async workIssueReservations(issueId, page = {}) {
|
|
14850
|
+
return workExecutionReservationPageSchema.parse(
|
|
14851
|
+
await this.request(`/work/issues/${encodeURIComponent(issueId)}/reservations${workQuery(page)}`)
|
|
14852
|
+
);
|
|
14853
|
+
}
|
|
14854
|
+
async workReservation(id) {
|
|
14855
|
+
return workExecutionReservationSchema.parse(await this.request(`/work/reservations/${encodeURIComponent(id)}`));
|
|
14856
|
+
}
|
|
14857
|
+
async workReservationBindings(id, page = {}) {
|
|
14858
|
+
return workExecutionBindingPageSchema.parse(await this.request(`/work/reservations/${encodeURIComponent(id)}/bindings${workQuery(page)}`));
|
|
14859
|
+
}
|
|
14860
|
+
async workReservationActivity(id, page = {}) {
|
|
14861
|
+
return workReservationActivityPageSchema.parse(await this.request(`/work/reservations/${encodeURIComponent(id)}/activity${workQuery(page)}`));
|
|
14862
|
+
}
|
|
14863
|
+
/** A Run's one authoritative binding; `not_found` for a Run admitted outside any canonical Issue. */
|
|
14864
|
+
async workRunBinding(runId) {
|
|
14865
|
+
return workExecutionBindingSchema.parse(await this.request(`/work/runs/${encodeURIComponent(runId)}/binding`));
|
|
14866
|
+
}
|
|
14867
|
+
/** Run control's record of a dispatched coordinated Run: grants, fencing refusal, receipts and physical stop (DRI-340). */
|
|
14868
|
+
async workRunControl(runId) {
|
|
14869
|
+
return runControlRecordSchema.parse(await this.request(`/work/runs/${encodeURIComponent(runId)}/control`));
|
|
14870
|
+
}
|
|
14871
|
+
/** `run_control.acknowledge_lost` (Team admin): a Run whose stop is unproven is accepted as no longer running (DRI-340). */
|
|
14872
|
+
async runControlCommand(command) {
|
|
14873
|
+
return runControlCommandResultSchema.parse(await this.request("/work/commands", runControlCommandSchema.parse(command)));
|
|
14874
|
+
}
|
|
14875
|
+
/** The Issue an exact legacy reference resolves to; anything else is `not_found`. */
|
|
14876
|
+
async workResolveLegacyAlias(alias) {
|
|
14877
|
+
return workLegacyAliasSchema.parse(await this.request(`/work/legacy-aliases/resolve?${new URLSearchParams({ alias })}`));
|
|
14878
|
+
}
|
|
14879
|
+
// ── end DRI-310 ──
|
|
14880
|
+
// ── Coordination (DRI-314) ──
|
|
14881
|
+
/**
|
|
14882
|
+
* Sends one Coordination command on the Work command route, with its receipts and replay: a member's
|
|
14883
|
+
* `execution.start`, `.control`, `.cancel`, `.close`, `.decide`, `.amend`, `.message`, or the provisional synthetic
|
|
14884
|
+
* driver's `coordinator.*` and `run.fact` (Team admin). A stale generation is `stale_generation`.
|
|
14885
|
+
*/
|
|
14886
|
+
async coordinationCommand(command) {
|
|
14887
|
+
return coordinationCommandResultSchema.parse(await this.request("/work/commands", coordinationCommandSchema.parse(command)));
|
|
14888
|
+
}
|
|
14889
|
+
async workExecution(id) {
|
|
14890
|
+
return executionSchema.parse(await this.request(`/work/executions/${encodeURIComponent(id)}`));
|
|
14891
|
+
}
|
|
14892
|
+
/** Every execution of an Issue, oldest first: its predecessors and the current one. */
|
|
14893
|
+
async workIssueExecutions(issueId, page = {}) {
|
|
14894
|
+
return executionPageSchema.parse(await this.request(`/work/issues/${encodeURIComponent(issueId)}/executions${workQuery(page)}`));
|
|
14895
|
+
}
|
|
14896
|
+
async workCoordinator(id) {
|
|
14897
|
+
return coordinatorSchema.parse(await this.request(`/work/coordinators/${encodeURIComponent(id)}`));
|
|
14898
|
+
}
|
|
14899
|
+
/** Measured spend across every execution of an Issue. */
|
|
14900
|
+
async workIssueSpend(issueId) {
|
|
14901
|
+
return coordinationSpendSchema.parse(await this.request(`/work/issues/${encodeURIComponent(issueId)}/spend`));
|
|
14902
|
+
}
|
|
14903
|
+
// ── Coordinator activation and specialist assignment (DRI-317) ──
|
|
14904
|
+
/**
|
|
14905
|
+
* With a coordinated Run's scoped credential (its grant's token as the bearer): what that Run may read about its own
|
|
14906
|
+
* work. The coordinator sees its execution's assignments, packages and reports; a specialist its own assignment.
|
|
14907
|
+
* A member's token is `forbidden`. Commands go through `coordinationCommand` with the same credential.
|
|
14908
|
+
*/
|
|
14909
|
+
async workBrief() {
|
|
14910
|
+
return coordinationBriefSchema.parse(await this.request("/work/brief"));
|
|
14911
|
+
}
|
|
14912
|
+
/** An execution's coordinator activation: its Run, binding and coordinator Workflow. */
|
|
14913
|
+
async workExecutionActivation(executionId) {
|
|
14914
|
+
return executionActivationSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/activation`));
|
|
14915
|
+
}
|
|
14916
|
+
/** The specialist reports exposed on an execution's canonical Thread, oldest first. */
|
|
14917
|
+
async workExecutionReports(executionId, page = {}) {
|
|
14918
|
+
return workPageSchema(assignmentReportSchema).parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/reports${workQuery(page)}`));
|
|
14919
|
+
}
|
|
14920
|
+
// ── end DRI-317 ──
|
|
14921
|
+
// ── Coordinator wake-up (DRI-318) ──
|
|
14922
|
+
/** An execution's coordinator inbox, every event oldest first, with each event's acknowledgement. */
|
|
14923
|
+
async workExecutionInbox(executionId, page = {}) {
|
|
14924
|
+
return workPageSchema(coordinationInboxEventSchema).parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/inbox${workQuery(page)}`));
|
|
14925
|
+
}
|
|
14926
|
+
/** An execution's InboxCursor: acknowledged through, newest event, wake-ups, the activation asleep. */
|
|
14927
|
+
async workExecutionCursor(executionId) {
|
|
14928
|
+
return inboxCursorSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/cursor`));
|
|
14929
|
+
}
|
|
14930
|
+
// ── end DRI-318 ──
|
|
14931
|
+
// ── Recovery of interrupted work (DRI-325) ──
|
|
14932
|
+
/** An execution's recovery episodes, oldest first, each with its append-only steps. */
|
|
14933
|
+
async workExecutionRecoveries(executionId, page = {}) {
|
|
14934
|
+
return workPageSchema(recoveryEpisodeSchema).parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/recoveries${workQuery(page)}`));
|
|
14935
|
+
}
|
|
14936
|
+
// ── end DRI-325 ──
|
|
14937
|
+
// ── Hold, pause and revise (DRI-323) ──
|
|
14938
|
+
/** An execution's control requests, oldest first: requested versus effective, each specialist Run's part, a hold's wait. */
|
|
14939
|
+
async workExecutionControls(executionId, page = {}) {
|
|
14940
|
+
return workPageSchema(controlRequestSchema).parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/controls${workQuery(page)}`));
|
|
14941
|
+
}
|
|
14942
|
+
/** An assignment's published revisions, oldest first, each with its message and incorporation. */
|
|
14943
|
+
async workAssignmentRevisions(assignmentId, page = {}) {
|
|
14944
|
+
return workPageSchema(assignmentRevisionSchema).parse(await this.request(`/work/assignments/${encodeURIComponent(assignmentId)}/revisions${workQuery(page)}`));
|
|
14945
|
+
}
|
|
14946
|
+
// ── end DRI-323 ──
|
|
14947
|
+
// ── DRI-324: an execution's newest cancellation ──
|
|
14948
|
+
/** Each admitted Run's tool-boundary hold and stop, every effect issued (finished, unknown), what remains and what blocks closure. */
|
|
14949
|
+
async workExecutionCancellation(executionId) {
|
|
14950
|
+
return executionCancellationSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/cancellation`));
|
|
14951
|
+
}
|
|
14952
|
+
// ── Execution workflows (DRI-319): the Team library and a pinned execution's steps ──
|
|
14953
|
+
/** Publishes an execution workflow revision (Team admin) on its own route, with the Work receipts and replay. */
|
|
14954
|
+
async executionWorkflowCommand(command) {
|
|
14955
|
+
return coordinationCommandResultSchema.parse(await this.request("/work/execution-workflows/commands", coordinationCommandSchema.parse(command)));
|
|
14956
|
+
}
|
|
14957
|
+
async executionWorkflows(page = {}) {
|
|
14958
|
+
return executionWorkflowPageSchema.parse(await this.request(`/work/execution-workflows${workQuery(page)}`));
|
|
14959
|
+
}
|
|
14960
|
+
async executionWorkflow(id) {
|
|
14961
|
+
return executionWorkflowSchema.parse(await this.request(`/work/execution-workflows/${encodeURIComponent(id)}`));
|
|
14962
|
+
}
|
|
14963
|
+
async executionWorkflowRevisions(id, page = {}) {
|
|
14964
|
+
return executionWorkflowRevisionPageSchema.parse(await this.request(`/work/execution-workflows/${encodeURIComponent(id)}/revisions${workQuery(page)}`));
|
|
14965
|
+
}
|
|
14966
|
+
async executionWorkflowRevision(id, revision) {
|
|
14967
|
+
return executionWorkflowRevisionSchema.parse(await this.request(`/work/execution-workflows/${encodeURIComponent(id)}/revisions/${revision}`));
|
|
14968
|
+
}
|
|
14969
|
+
/** The pinned workflow revision of an execution and every step's state. */
|
|
14970
|
+
async workExecutionSteps(executionId) {
|
|
14971
|
+
return executionStepsSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/steps`));
|
|
14972
|
+
}
|
|
14973
|
+
// ── end DRI-319 ──
|
|
14974
|
+
// ── Review and repair loop (DRI-320) ──
|
|
14975
|
+
/** An execution's review rounds, oldest first: each verdict, the candidate it evaluated, the findings. */
|
|
14976
|
+
async workExecutionReviews(executionId, page = {}) {
|
|
14977
|
+
return reviewRoundPageSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/reviews${workQuery(page)}`));
|
|
14978
|
+
}
|
|
14979
|
+
// ── end DRI-320 ──
|
|
14980
|
+
// ── Messages and receipts (DRI-322) ──
|
|
14981
|
+
/**
|
|
14982
|
+
* An execution's messages, oldest first: a specialist's with its target and every receipt (received, delivered,
|
|
14983
|
+
* incorporated, rejected, unconfirmed), never relabelled. Send with `coordinationCommand` (`message.send`).
|
|
14984
|
+
*/
|
|
14985
|
+
async workExecutionMessages(executionId, page = {}) {
|
|
14986
|
+
return workPageSchema(executionMessageSchema).parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/messages${workQuery(page)}`));
|
|
14987
|
+
}
|
|
14988
|
+
// ── end DRI-322 ──
|
|
14989
|
+
// ── DRI-329: an execution's place in the delegation hierarchy ──
|
|
14990
|
+
/** Depth below its root, remaining delegation depth, subtree spend and a page of child executions with state and spend. */
|
|
14991
|
+
async workExecutionHierarchy(executionId, page = {}) {
|
|
14992
|
+
return executionHierarchySchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/hierarchy${workQuery(page)}`));
|
|
14993
|
+
}
|
|
14994
|
+
/** The child executions an execution's assignments control, oldest first. */
|
|
14995
|
+
async workExecutionChildren(executionId, page = {}) {
|
|
14996
|
+
return executionPageSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/children${workQuery(page)}`));
|
|
14997
|
+
}
|
|
14998
|
+
// ── end DRI-329 ──
|
|
14999
|
+
// ── QA steps and repair (DRI-321) ──
|
|
15000
|
+
/** An execution's QA rounds, oldest first: attempt, deployment, candidate, outcome, reproduction. */
|
|
15001
|
+
async workExecutionQa(executionId, page = {}) {
|
|
15002
|
+
return qaRoundPageSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/qa${workQuery(page)}`));
|
|
15003
|
+
}
|
|
15004
|
+
// ── end DRI-321 ──
|
|
15005
|
+
// ── Questions routed to their authority (DRI-330) ──
|
|
15006
|
+
/** Every question visible on an execution (raised in it or routed through it), in the order it reached it. Answer with `coordinationCommand`. */
|
|
15007
|
+
async workExecutionQuestions(executionId, page = {}) {
|
|
15008
|
+
return executionQuestionPageSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/questions${workQuery(page)}`));
|
|
15009
|
+
}
|
|
15010
|
+
/** One question: its authority, addressee, route, answer and its delivery, resolution. */
|
|
15011
|
+
async workQuestion(questionId) {
|
|
15012
|
+
return executionQuestionSchema.parse(await this.request(`/work/questions/${encodeURIComponent(questionId)}`));
|
|
15013
|
+
}
|
|
15014
|
+
// ── end DRI-330 ──
|
|
15015
|
+
// ── Child acceptance and delivery readiness (DRI-335) ──
|
|
15016
|
+
/** An execution's outcomes as distinct facts: finished, awaiting acceptance, accepted, closure, and Work's delivery readiness. */
|
|
15017
|
+
async workExecutionOutcome(executionId) {
|
|
15018
|
+
return executionOutcomeSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/outcome`));
|
|
15019
|
+
}
|
|
15020
|
+
/** An execution's completion packages, oldest first, each with the versioned assignment contract it answers. */
|
|
15021
|
+
async workExecutionPackages(executionId, page = {}) {
|
|
15022
|
+
return workPageSchema(completionPackageSchema).parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/packages${workQuery(page)}`));
|
|
15023
|
+
}
|
|
15024
|
+
// ── end DRI-335 ──
|
|
15025
|
+
// ── Client integration (DRI-342) ──
|
|
15026
|
+
/** Every execution of a Plan, oldest first. */
|
|
15027
|
+
async workPlanExecutions(planId, page = {}) {
|
|
15028
|
+
return executionPageSchema.parse(await this.request(`/work/plans/${encodeURIComponent(planId)}/executions${workQuery(page)}`));
|
|
15029
|
+
}
|
|
15030
|
+
/** One assignment. */
|
|
15031
|
+
async workAssignment(id) {
|
|
15032
|
+
return assignmentSchema.parse(await this.request(`/work/assignments/${encodeURIComponent(id)}`));
|
|
15033
|
+
}
|
|
15034
|
+
/** A page of an execution's assignments, oldest first. */
|
|
15035
|
+
async workExecutionAssignments(executionId, page = {}) {
|
|
15036
|
+
return assignmentPageSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/assignments${workQuery(page)}`));
|
|
15037
|
+
}
|
|
15038
|
+
/** A page of an execution's Activity, oldest first: its durable history. */
|
|
15039
|
+
async workExecutionActivity(executionId, page = {}) {
|
|
15040
|
+
return coordinationActivityPageSchema.parse(await this.request(`/work/executions/${encodeURIComponent(executionId)}/activity${workQuery(page)}`));
|
|
15041
|
+
}
|
|
15042
|
+
/**
|
|
15043
|
+
* The Team's authority record: who owns work-domain writes and starts, and the client contract the Hub serves.
|
|
15044
|
+
* Readable whether or not the Work core is on. A Hub that predates it answers `not_found`; see `WorkFlows.path`.
|
|
15045
|
+
*/
|
|
15046
|
+
async workAuthority() {
|
|
15047
|
+
return workAuthoritySchema.parse(await this.request("/work/authority"));
|
|
15048
|
+
}
|
|
15049
|
+
// ── end DRI-342 ──
|
|
15050
|
+
// ── end DRI-314 ──
|
|
15051
|
+
// ── Discussion on Work Threads (DRI-303) ──
|
|
15052
|
+
/**
|
|
15053
|
+
* Sends one discussion command (`comment.add`, `comment.edit`, `comment.delete`, `attachment.link`,
|
|
15054
|
+
* `attachment.check`, `attachment.remove`, `transcript.link`) on the Work command route, with its receipts and replay.
|
|
15055
|
+
*/
|
|
15056
|
+
async workDiscussionCommand(command) {
|
|
15057
|
+
return workDiscussionCommandResultSchema.parse(await this.request("/work/commands", workDiscussionCommandSchema.parse(command)));
|
|
15058
|
+
}
|
|
15059
|
+
/** The Thread's binding: the one Issue or Plan it belongs to. */
|
|
15060
|
+
async workThread(id) {
|
|
15061
|
+
return workThreadSchema.parse(await this.request(`/work/threads/${encodeURIComponent(id)}`));
|
|
15062
|
+
}
|
|
15063
|
+
/** A Thread's comments, oldest first, deleted ones as tombstones; `parentId` pages the replies of one comment. */
|
|
15064
|
+
async workThreadComments(id, page = {}) {
|
|
15065
|
+
return workCommentPageSchema.parse(await this.request(`/work/threads/${encodeURIComponent(id)}/comments${discussionQuery(page)}`));
|
|
15066
|
+
}
|
|
15067
|
+
/** A Thread's link-only attachments, oldest first; `commentId` narrows to one comment's. */
|
|
15068
|
+
async workThreadAttachments(id, page = {}) {
|
|
15069
|
+
return workAttachmentPageSchema.parse(await this.request(`/work/threads/${encodeURIComponent(id)}/attachments${discussionQuery(page)}`));
|
|
15070
|
+
}
|
|
15071
|
+
/** A Thread's transcript links, each `visible` or `restricted` by the caller's access to its Run. */
|
|
15072
|
+
async workThreadTranscriptLinks(id, page = {}) {
|
|
15073
|
+
return workTranscriptLinkPageSchema.parse(await this.request(`/work/threads/${encodeURIComponent(id)}/transcript-links${workQuery(page)}`));
|
|
15074
|
+
}
|
|
15075
|
+
async workComment(id) {
|
|
15076
|
+
return workCommentSchema.parse(await this.request(`/work/comments/${encodeURIComponent(id)}`));
|
|
15077
|
+
}
|
|
15078
|
+
async workCommentRevisions(id, page = {}) {
|
|
15079
|
+
return workCommentRevisionPageSchema.parse(await this.request(`/work/comments/${encodeURIComponent(id)}/revisions${workQuery(page)}`));
|
|
15080
|
+
}
|
|
15081
|
+
async workAttachment(id) {
|
|
15082
|
+
return workAttachmentSchema.parse(await this.request(`/work/attachments/${encodeURIComponent(id)}`));
|
|
15083
|
+
}
|
|
15084
|
+
async workAttachmentChecks(id, page = {}) {
|
|
15085
|
+
return workAttachmentCheckPageSchema.parse(await this.request(`/work/attachments/${encodeURIComponent(id)}/checks${workQuery(page)}`));
|
|
15086
|
+
}
|
|
15087
|
+
async workTranscriptLink(id) {
|
|
15088
|
+
return workTranscriptLinkSchema.parse(await this.request(`/work/transcript-links/${encodeURIComponent(id)}`));
|
|
15089
|
+
}
|
|
15090
|
+
/** The linked range of the Run's history, under the Run's own visibility; `after` is the last sequence read. */
|
|
15091
|
+
async workTranscriptHistory(id, after) {
|
|
15092
|
+
return workTranscriptHistorySchema.parse(await this.request(
|
|
15093
|
+
`/work/transcript-links/${encodeURIComponent(id)}/history${after === void 0 ? "" : `?after=${after}`}`
|
|
15094
|
+
));
|
|
15095
|
+
}
|
|
15096
|
+
async workDiscussionActivity(subject, page = {}) {
|
|
15097
|
+
const collection = { comment: "comments", attachment: "attachments", transcript_link: "transcript-links" }[subject.kind];
|
|
15098
|
+
return workDiscussionActivityPageSchema.parse(
|
|
15099
|
+
await this.request(`/work/${collection}/${encodeURIComponent(subject.id)}/activity${workQuery(page)}`)
|
|
15100
|
+
);
|
|
15101
|
+
}
|
|
15102
|
+
// ── end DRI-303 ──
|
|
15103
|
+
// ── Normalized import preview (DRI-305) ──
|
|
15104
|
+
/** Previews one normalized import (`import.preview`, Team admin) on its own route, with the Work receipts. */
|
|
15105
|
+
async workImportCommand(command) {
|
|
15106
|
+
return workImportCommandResultSchema.parse(await this.request("/work/imports/commands", workImportCommandSchema.parse(command)));
|
|
15107
|
+
}
|
|
15108
|
+
async workImports(page = {}) {
|
|
15109
|
+
return workImportBatchPageSchema.parse(await this.request(`/work/imports${workQuery(page)}`));
|
|
15110
|
+
}
|
|
15111
|
+
async workImport(id) {
|
|
15112
|
+
return workImportBatchSchema.parse(await this.request(`/work/imports/${encodeURIComponent(id)}`));
|
|
15113
|
+
}
|
|
15114
|
+
/** A batch's previewed items, optionally of one collection or one disposition. */
|
|
15115
|
+
async workImportItems(id, page = {}) {
|
|
15116
|
+
return workImportItemPageSchema.parse(await this.request(`/work/imports/${encodeURIComponent(id)}/items${sourceQuery(page)}`));
|
|
15117
|
+
}
|
|
15118
|
+
/** The frozen envelope and mapping, exactly as previewed, with their digests. */
|
|
15119
|
+
async workImportSnapshot(id) {
|
|
15120
|
+
return workImportSnapshotSchema.parse(await this.request(`/work/imports/${encodeURIComponent(id)}/snapshot`));
|
|
15121
|
+
}
|
|
15122
|
+
// ── end DRI-305 ──
|
|
15123
|
+
// ── Import commit (DRI-306): `import.commit` goes through `workImportCommand` ──
|
|
15124
|
+
/** What the commit did with a batch, item by item and part by part, optionally filtered. */
|
|
15125
|
+
async workImportOutcomes(id, page = {}) {
|
|
15126
|
+
return workImportOutcomePageSchema.parse(await this.request(`/work/imports/${encodeURIComponent(id)}/outcomes${sourceQuery(page)}`));
|
|
15127
|
+
}
|
|
15128
|
+
// ── end DRI-306 ──
|
|
15129
|
+
// ── Import completion (DRI-307): `import.complete` goes through `workImportCommand` ──
|
|
15130
|
+
/** A committed batch's discussion components and their current states, optionally of one collection or state. */
|
|
15131
|
+
async workImportComponents(id, page = {}) {
|
|
15132
|
+
return workImportComponentPageSchema.parse(await this.request(`/work/imports/${encodeURIComponent(id)}/components${sourceQuery(page)}`));
|
|
15133
|
+
}
|
|
15134
|
+
// ── end DRI-307 ──
|
|
13918
15135
|
/** The recent Runs; with a filter, every Run carrying that key or every child of that Run, newest first. */
|
|
13919
15136
|
async runs(filter) {
|
|
13920
15137
|
const query = !filter ? "" : "key" in filter ? `?key=${encodeURIComponent(filter.key)}` : `?parent=${encodeURIComponent(filter.parent)}`;
|
|
@@ -15607,51 +16824,1383 @@ ${USAGE3}
|
|
|
15607
16824
|
}
|
|
15608
16825
|
}
|
|
15609
16826
|
|
|
15610
|
-
// src/app/entry/
|
|
15611
|
-
import
|
|
15612
|
-
|
|
15613
|
-
|
|
16827
|
+
// src/app/entry/driveCommand.ts
|
|
16828
|
+
import path14 from "path";
|
|
16829
|
+
|
|
16830
|
+
// src/core/drive/merge.ts
|
|
16831
|
+
var DEFAULT_TIMEOUT_MS = 9 * 6e4;
|
|
16832
|
+
var DEFAULT_POLL_MS = 15e3;
|
|
16833
|
+
var DEFAULT_NO_CHECKS_GRACE_MS = 45e3;
|
|
16834
|
+
var DEFAULT_SETTLE_MS = 2e4;
|
|
16835
|
+
var DEFAULT_LOCK_WAIT_MS = 6e4;
|
|
16836
|
+
var MAX_ROUNDS = 2;
|
|
16837
|
+
var DEPENDENCY_FILES = [
|
|
16838
|
+
"package.json",
|
|
16839
|
+
"package-lock.json",
|
|
16840
|
+
"npm-shrinkwrap.json",
|
|
16841
|
+
"pnpm-lock.yaml",
|
|
16842
|
+
"yarn.lock",
|
|
16843
|
+
"bun.lock",
|
|
16844
|
+
"bun.lockb"
|
|
16845
|
+
];
|
|
16846
|
+
var OUTPUT_TAIL = 4e3;
|
|
16847
|
+
var SCRATCH_PREFIX = "drisp-drive-merge-";
|
|
16848
|
+
var MERGED_REFS = "refs/drisp/merged";
|
|
16849
|
+
var TESTED_REFS = "refs/drisp/tested";
|
|
16850
|
+
function tail(text) {
|
|
16851
|
+
const trimmed = text.trim();
|
|
16852
|
+
return trimmed.length > OUTPUT_TAIL ? `\u2026${trimmed.slice(-OUTPUT_TAIL)}` : trimmed;
|
|
16853
|
+
}
|
|
16854
|
+
function both(result) {
|
|
16855
|
+
return tail([result.stdout, result.stderr].filter(Boolean).join("\n"));
|
|
16856
|
+
}
|
|
16857
|
+
var Stop = class extends Error {
|
|
16858
|
+
constructor(result) {
|
|
16859
|
+
super(result.detail);
|
|
16860
|
+
this.result = result;
|
|
16861
|
+
}
|
|
16862
|
+
result;
|
|
16863
|
+
};
|
|
16864
|
+
function stop(outcome, detail, output) {
|
|
16865
|
+
throw new Stop({ outcome, detail, ...output ? { output } : {} });
|
|
16866
|
+
}
|
|
16867
|
+
function refuse(detail, output) {
|
|
16868
|
+
return stop("refused", detail, output);
|
|
16869
|
+
}
|
|
16870
|
+
function baseKeptMoving(defaultBranch) {
|
|
16871
|
+
return `base kept moving: ${defaultBranch} moved after each of ${MAX_ROUNDS} rebase-and-check rounds; run the command again`;
|
|
16872
|
+
}
|
|
16873
|
+
async function driveMerge(input, deps) {
|
|
15614
16874
|
try {
|
|
15615
|
-
|
|
15616
|
-
throw new Error(
|
|
15617
|
-
"This command requires a native Issue Run with DRISP_HUB_URL and DRISP_RUN_TOKEN."
|
|
15618
|
-
);
|
|
15619
|
-
const client2 = DrispClient.delegated(
|
|
15620
|
-
env2.DRISP_HUB_URL,
|
|
15621
|
-
env2.DRISP_RUN_TOKEN
|
|
15622
|
-
);
|
|
15623
|
-
const [action, argument, ...extra] = args;
|
|
15624
|
-
if (!argument || extra.length) throw new Error(usage2);
|
|
15625
|
-
if (action === "get")
|
|
15626
|
-
process.stdout.write(
|
|
15627
|
-
JSON.stringify(await client2.trackerIssue(argument)) + "\n"
|
|
15628
|
-
);
|
|
15629
|
-
else if (action === "apply") {
|
|
15630
|
-
const operation = trackerOperationSchema.parse(
|
|
15631
|
-
JSON.parse(await readFile2(argument, "utf8"))
|
|
15632
|
-
);
|
|
15633
|
-
process.stdout.write(
|
|
15634
|
-
JSON.stringify(await client2.trackerOperation(operation)) + "\n"
|
|
15635
|
-
);
|
|
15636
|
-
} else throw new Error(usage2);
|
|
15637
|
-
return 0;
|
|
16875
|
+
return await new DriveMerge(input, deps).run();
|
|
15638
16876
|
} catch (error) {
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
);
|
|
15642
|
-
return 2;
|
|
16877
|
+
if (error instanceof Stop) return error.result;
|
|
16878
|
+
throw error;
|
|
15643
16879
|
}
|
|
15644
16880
|
}
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
15653
|
-
|
|
15654
|
-
|
|
16881
|
+
function judge(checks, scope) {
|
|
16882
|
+
const { failing, unfinished } = judgeChecks(checks);
|
|
16883
|
+
if (failing.length > 0)
|
|
16884
|
+
return {
|
|
16885
|
+
kind: "failed",
|
|
16886
|
+
detail: `${scope} failed: ${failing.map((check) => check.name).join(", ")}`
|
|
16887
|
+
};
|
|
16888
|
+
const cancelled = unfinished.filter((check) => check.bucket === "cancel");
|
|
16889
|
+
if (cancelled.length > 0)
|
|
16890
|
+
return {
|
|
16891
|
+
kind: "failed",
|
|
16892
|
+
detail: `${scope} cancelled: ${cancelled.map((check) => check.name).join(", ")}`
|
|
16893
|
+
};
|
|
16894
|
+
if (unfinished.length > 0)
|
|
16895
|
+
return {
|
|
16896
|
+
kind: "waiting",
|
|
16897
|
+
detail: `${scope} not finished: ${describeChecks(unfinished)}`
|
|
16898
|
+
};
|
|
16899
|
+
return {
|
|
16900
|
+
kind: "passed",
|
|
16901
|
+
scope,
|
|
16902
|
+
detail: `${scope} passed (${checks.map((check) => check.name).join(", ")})`
|
|
16903
|
+
};
|
|
16904
|
+
}
|
|
16905
|
+
var DriveMerge = class {
|
|
16906
|
+
constructor(input, deps) {
|
|
16907
|
+
this.input = input;
|
|
16908
|
+
this.deps = deps;
|
|
16909
|
+
this.timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
16910
|
+
this.pollMs = input.pollMs ?? DEFAULT_POLL_MS;
|
|
16911
|
+
this.graceMs = input.noChecksGraceMs ?? DEFAULT_NO_CHECKS_GRACE_MS;
|
|
16912
|
+
this.settleMs = input.settleMs ?? DEFAULT_SETTLE_MS;
|
|
16913
|
+
}
|
|
16914
|
+
input;
|
|
16915
|
+
deps;
|
|
16916
|
+
timeoutMs;
|
|
16917
|
+
/** Set when the command starts; every stage takes what is left of it. */
|
|
16918
|
+
deadline = 0;
|
|
16919
|
+
pollMs;
|
|
16920
|
+
graceMs;
|
|
16921
|
+
settleMs;
|
|
16922
|
+
left() {
|
|
16923
|
+
return this.deadline - this.deps.now();
|
|
16924
|
+
}
|
|
16925
|
+
timedOut(what) {
|
|
16926
|
+
return stop(
|
|
16927
|
+
"checks_timeout",
|
|
16928
|
+
`${what}: the command's ${Math.round(this.timeoutMs / 1e3)} s deadline passed; run the command again`
|
|
16929
|
+
);
|
|
16930
|
+
}
|
|
16931
|
+
git(cwd, ...args) {
|
|
16932
|
+
return this.deps.git(cwd, args);
|
|
16933
|
+
}
|
|
16934
|
+
/** stdout of a git call that must succeed, else `refused` naming it. */
|
|
16935
|
+
must(cwd, what, ...args) {
|
|
16936
|
+
const result = this.git(cwd, ...args);
|
|
16937
|
+
if (result.code !== 0) refuse(`${what} failed`, both(result));
|
|
16938
|
+
return result.stdout.trim();
|
|
16939
|
+
}
|
|
16940
|
+
sha(cwd, ref) {
|
|
16941
|
+
const result = this.git(cwd, "rev-parse", "--verify", "--quiet", ref);
|
|
16942
|
+
return result.code === 0 ? result.stdout.trim() : null;
|
|
16943
|
+
}
|
|
16944
|
+
isAncestor(cwd, commit, of) {
|
|
16945
|
+
return this.git(cwd, "merge-base", "--is-ancestor", commit, of).code === 0;
|
|
16946
|
+
}
|
|
16947
|
+
checkedOut(cwd) {
|
|
16948
|
+
return this.git(
|
|
16949
|
+
cwd,
|
|
16950
|
+
"symbolic-ref",
|
|
16951
|
+
"--quiet",
|
|
16952
|
+
"--short",
|
|
16953
|
+
"HEAD"
|
|
16954
|
+
).stdout.trim();
|
|
16955
|
+
}
|
|
16956
|
+
rebaseInProgress(worktreeDir) {
|
|
16957
|
+
return ["rebase-merge", "rebase-apply"].some((state) => {
|
|
16958
|
+
const at = this.git(
|
|
16959
|
+
worktreeDir,
|
|
16960
|
+
"rev-parse",
|
|
16961
|
+
"--path-format=absolute",
|
|
16962
|
+
"--git-path",
|
|
16963
|
+
state
|
|
16964
|
+
).stdout.trim();
|
|
16965
|
+
return at !== "" && this.deps.exists(at);
|
|
16966
|
+
});
|
|
16967
|
+
}
|
|
16968
|
+
async run() {
|
|
16969
|
+
const { worktree, item } = this.input;
|
|
16970
|
+
this.deadline = this.deps.now() + this.timeoutMs;
|
|
16971
|
+
const top = this.git(worktree, "rev-parse", "--show-toplevel");
|
|
16972
|
+
if (top.code !== 0) refuse(`${worktree} is not a git worktree`, both(top));
|
|
16973
|
+
const worktreeDir = top.stdout.trim();
|
|
16974
|
+
if (this.git(worktreeDir, "check-ref-format", `${MERGED_REFS}/${item}`).code !== 0)
|
|
16975
|
+
refuse(`"${item}" cannot name a git ref; use the item's identifier`);
|
|
16976
|
+
const commonDir = this.must(
|
|
16977
|
+
worktreeDir,
|
|
16978
|
+
"reading the git common dir",
|
|
16979
|
+
"rev-parse",
|
|
16980
|
+
"--path-format=absolute",
|
|
16981
|
+
"--git-common-dir"
|
|
16982
|
+
);
|
|
16983
|
+
const release2 = await this.deps.lock(
|
|
16984
|
+
commonDir,
|
|
16985
|
+
Math.min(this.input.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS, this.left())
|
|
16986
|
+
);
|
|
16987
|
+
if (!release2)
|
|
16988
|
+
refuse(
|
|
16989
|
+
"another merge in progress: another drisp drive merge holds the lock in this repository; run the command again once it finishes"
|
|
16990
|
+
);
|
|
16991
|
+
try {
|
|
16992
|
+
this.removeStaleScratch(worktreeDir);
|
|
16993
|
+
return await this.locked(worktreeDir);
|
|
16994
|
+
} finally {
|
|
16995
|
+
release2();
|
|
16996
|
+
}
|
|
16997
|
+
}
|
|
16998
|
+
/** Temporary checkouts a killed run left behind (they live under `drisp-drive-merge-*`). */
|
|
16999
|
+
removeStaleScratch(worktreeDir) {
|
|
17000
|
+
const stale = this.git(worktreeDir, "worktree", "list", "--porcelain").stdout.split("\n").filter(
|
|
17001
|
+
(line) => line.startsWith("worktree ") && line.includes(SCRATCH_PREFIX)
|
|
17002
|
+
).map((line) => line.slice("worktree ".length));
|
|
17003
|
+
for (const dir of stale)
|
|
17004
|
+
this.git(worktreeDir, "worktree", "remove", "--force", dir);
|
|
17005
|
+
this.git(worktreeDir, "worktree", "prune");
|
|
17006
|
+
}
|
|
17007
|
+
async locked(worktreeDir) {
|
|
17008
|
+
const { item } = this.input;
|
|
17009
|
+
if (this.rebaseInProgress(worktreeDir))
|
|
17010
|
+
refuse(
|
|
17011
|
+
"a rebase is in progress in the worktree; run `git rebase --abort` there (or finish it), then merge again"
|
|
17012
|
+
);
|
|
17013
|
+
const mainCheckout = this.mainCheckout(worktreeDir);
|
|
17014
|
+
const current = this.checkedOut(worktreeDir);
|
|
17015
|
+
const branch = this.input.branch ?? current;
|
|
17016
|
+
if (!branch)
|
|
17017
|
+
refuse("the worktree has a detached HEAD; check out the item branch");
|
|
17018
|
+
if (current !== branch)
|
|
17019
|
+
refuse(
|
|
17020
|
+
`the worktree has ${current || "a detached HEAD"} checked out, not ${branch}`
|
|
17021
|
+
);
|
|
17022
|
+
const originUrl = this.git(
|
|
17023
|
+
mainCheckout,
|
|
17024
|
+
"config",
|
|
17025
|
+
"--get",
|
|
17026
|
+
"remote.origin.url"
|
|
17027
|
+
);
|
|
17028
|
+
const hasOrigin = originUrl.code === 0;
|
|
17029
|
+
const defaultBranch = this.defaultBranch(mainCheckout, hasOrigin);
|
|
17030
|
+
if (branch === defaultBranch)
|
|
17031
|
+
refuse(`${branch} is the default branch; name the item's branch`);
|
|
17032
|
+
const dirty = this.uncommitted(worktreeDir);
|
|
17033
|
+
if (dirty.length > 0)
|
|
17034
|
+
stop(
|
|
17035
|
+
"conflict",
|
|
17036
|
+
"the worktree has uncommitted changes or untracked files; commit them (or remove or ignore them), then merge again",
|
|
17037
|
+
dirty.join("\n")
|
|
17038
|
+
);
|
|
17039
|
+
const github = /github\.com[:/]/i.test(originUrl.stdout);
|
|
17040
|
+
this.deps.log(
|
|
17041
|
+
`drive merge ${item}: ${branch} \u2192 ${defaultBranch} (${github ? "GitHub" : hasOrigin ? "git origin" : "local"})`
|
|
17042
|
+
);
|
|
17043
|
+
const ctx = { worktreeDir, mainCheckout, branch, defaultBranch, hasOrigin };
|
|
17044
|
+
return github ? this.viaGitHub(ctx) : this.viaGit(ctx);
|
|
17045
|
+
}
|
|
17046
|
+
/** The main checkout: the first entry of `git worktree list`. */
|
|
17047
|
+
mainCheckout(worktreeDir) {
|
|
17048
|
+
const list = this.must(
|
|
17049
|
+
worktreeDir,
|
|
17050
|
+
"git worktree list",
|
|
17051
|
+
"worktree",
|
|
17052
|
+
"list",
|
|
17053
|
+
"--porcelain"
|
|
17054
|
+
);
|
|
17055
|
+
const first = list.split("\n").find((line) => line.startsWith("worktree "))?.slice("worktree ".length);
|
|
17056
|
+
if (!first) refuse("could not find the main checkout");
|
|
17057
|
+
return first;
|
|
17058
|
+
}
|
|
17059
|
+
/** origin's HEAD, else the main checkout's `main`/`master`. */
|
|
17060
|
+
defaultBranch(mainCheckout, hasOrigin) {
|
|
17061
|
+
const head = this.git(
|
|
17062
|
+
mainCheckout,
|
|
17063
|
+
"symbolic-ref",
|
|
17064
|
+
"--quiet",
|
|
17065
|
+
"--short",
|
|
17066
|
+
"refs/remotes/origin/HEAD"
|
|
17067
|
+
).stdout.trim();
|
|
17068
|
+
if (head.startsWith("origin/")) return head.slice("origin/".length);
|
|
17069
|
+
if (hasOrigin) {
|
|
17070
|
+
const remote = this.git(
|
|
17071
|
+
mainCheckout,
|
|
17072
|
+
"ls-remote",
|
|
17073
|
+
"--symref",
|
|
17074
|
+
"origin",
|
|
17075
|
+
"HEAD"
|
|
17076
|
+
).stdout.match(/^ref: refs\/heads\/(\S+)\s+HEAD/m);
|
|
17077
|
+
if (remote?.[1]) return remote[1];
|
|
17078
|
+
}
|
|
17079
|
+
for (const name of ["main", "master"]) {
|
|
17080
|
+
if (this.sha(mainCheckout, `refs/heads/${name}`)) return name;
|
|
17081
|
+
}
|
|
17082
|
+
return refuse("could not tell the default branch (no main or master)");
|
|
17083
|
+
}
|
|
17084
|
+
status(cwd, untracked) {
|
|
17085
|
+
return this.git(
|
|
17086
|
+
cwd,
|
|
17087
|
+
"status",
|
|
17088
|
+
"--porcelain",
|
|
17089
|
+
`--untracked-files=${untracked}`
|
|
17090
|
+
).stdout.split("\n").map((line) => line.slice(3).trim()).filter(Boolean);
|
|
17091
|
+
}
|
|
17092
|
+
/** Changes to tracked files and untracked, non-ignored files: neither may ride along unseen. */
|
|
17093
|
+
uncommitted(cwd) {
|
|
17094
|
+
return this.status(cwd, "all");
|
|
17095
|
+
}
|
|
17096
|
+
/** Rebase the worktree onto `onto`; a conflict is aborted and reported. Returns the rebased HEAD. */
|
|
17097
|
+
rebase(worktreeDir, onto) {
|
|
17098
|
+
const rebased = this.git(worktreeDir, "rebase", onto);
|
|
17099
|
+
if (rebased.code !== 0) {
|
|
17100
|
+
const files = this.git(
|
|
17101
|
+
worktreeDir,
|
|
17102
|
+
"diff",
|
|
17103
|
+
"--name-only",
|
|
17104
|
+
"--diff-filter=U"
|
|
17105
|
+
).stdout.split("\n").filter(Boolean);
|
|
17106
|
+
if (this.rebaseInProgress(worktreeDir))
|
|
17107
|
+
this.git(worktreeDir, "rebase", "--abort");
|
|
17108
|
+
if (files.length === 0)
|
|
17109
|
+
refuse(`git rebase ${onto} failed (not a conflict)`, both(rebased));
|
|
17110
|
+
stop(
|
|
17111
|
+
"conflict",
|
|
17112
|
+
`rebasing onto ${onto} conflicts in ${files.join(", ")}; the rebase was aborted`,
|
|
17113
|
+
files.join("\n")
|
|
17114
|
+
);
|
|
17115
|
+
}
|
|
17116
|
+
return this.must(worktreeDir, "reading HEAD", "rev-parse", "HEAD");
|
|
17117
|
+
}
|
|
17118
|
+
/** The repository's own checks: `.athena/config.json` `checks`, else package.json's `test` script. */
|
|
17119
|
+
repoChecks(mainCheckout, worktreeDir) {
|
|
17120
|
+
const configured = this.deps.configuredChecks(mainCheckout);
|
|
17121
|
+
if (configured && configured.length > 0) return configured;
|
|
17122
|
+
if (this.deps.packageTestScript(worktreeDir) !== void 0)
|
|
17123
|
+
return ["npm test"];
|
|
17124
|
+
return refuse(
|
|
17125
|
+
'no checks to prove the merge: set "checks" in .athena/config.json or a package.json test script'
|
|
17126
|
+
);
|
|
17127
|
+
}
|
|
17128
|
+
/** The manifests and lockfiles whose change means the worktree's installed dependencies may not match. */
|
|
17129
|
+
dependenciesChanged(cwd, base, sha) {
|
|
17130
|
+
const changed = this.git(
|
|
17131
|
+
cwd,
|
|
17132
|
+
"diff",
|
|
17133
|
+
"--name-only",
|
|
17134
|
+
base,
|
|
17135
|
+
sha,
|
|
17136
|
+
"--",
|
|
17137
|
+
...DEPENDENCY_FILES.map((name) => `:(glob)**/${name}`)
|
|
17138
|
+
).stdout.trim();
|
|
17139
|
+
return changed !== "";
|
|
17140
|
+
}
|
|
17141
|
+
/** The install the checkout's lockfile calls for, scripts off. */
|
|
17142
|
+
installCommand(dir) {
|
|
17143
|
+
const has = (name) => this.deps.exists(`${dir}/${name}`);
|
|
17144
|
+
if (has("pnpm-lock.yaml"))
|
|
17145
|
+
return "pnpm install --frozen-lockfile --ignore-scripts";
|
|
17146
|
+
if (has("yarn.lock"))
|
|
17147
|
+
return "yarn install --frozen-lockfile --ignore-scripts";
|
|
17148
|
+
if (has("bun.lock") || has("bun.lockb"))
|
|
17149
|
+
return "bun install --frozen-lockfile --ignore-scripts";
|
|
17150
|
+
if (has("package-lock.json") || has("npm-shrinkwrap.json"))
|
|
17151
|
+
return "npm ci --ignore-scripts --no-audit --no-fund";
|
|
17152
|
+
return "npm install --ignore-scripts --no-audit --no-fund";
|
|
17153
|
+
}
|
|
17154
|
+
/**
|
|
17155
|
+
* Run each check in a clean, detached checkout of `sha` (never the worktree, whose files may differ from the
|
|
17156
|
+
* commit); the first non-zero exit fails the merge, and the deadline is `checks_timeout`. The worktree's
|
|
17157
|
+
* `node_modules` is reused only when no manifest or lockfile changed since `base`; otherwise the lockfile's
|
|
17158
|
+
* install runs in the checkout first.
|
|
17159
|
+
*/
|
|
17160
|
+
async runRepoChecks(mainCheckout, worktreeDir, base, sha) {
|
|
17161
|
+
const checks = this.repoChecks(mainCheckout, worktreeDir);
|
|
17162
|
+
const scratch = this.deps.scratchDir();
|
|
17163
|
+
const added = this.git(
|
|
17164
|
+
worktreeDir,
|
|
17165
|
+
"worktree",
|
|
17166
|
+
"add",
|
|
17167
|
+
"--quiet",
|
|
17168
|
+
"--detach",
|
|
17169
|
+
scratch.dir,
|
|
17170
|
+
sha
|
|
17171
|
+
);
|
|
17172
|
+
if (added.code !== 0) {
|
|
17173
|
+
scratch.cleanup();
|
|
17174
|
+
refuse("could not check out the tested commit", both(added));
|
|
17175
|
+
}
|
|
17176
|
+
const run = async (command, what) => {
|
|
17177
|
+
if (this.left() <= 0) this.timedOut(`before ${what}`);
|
|
17178
|
+
const result = await this.deps.runCheck(
|
|
17179
|
+
scratch.dir,
|
|
17180
|
+
command,
|
|
17181
|
+
this.left()
|
|
17182
|
+
);
|
|
17183
|
+
if (result.timedOut)
|
|
17184
|
+
stop(
|
|
17185
|
+
"checks_timeout",
|
|
17186
|
+
`${what} was killed at the command's ${Math.round(this.timeoutMs / 1e3)} s deadline; run the command again`,
|
|
17187
|
+
both(result)
|
|
17188
|
+
);
|
|
17189
|
+
if (result.code !== 0)
|
|
17190
|
+
stop("checks_failed", `${what} exited ${result.code}`, both(result));
|
|
17191
|
+
};
|
|
17192
|
+
try {
|
|
17193
|
+
if (this.dependenciesChanged(worktreeDir, base, sha)) {
|
|
17194
|
+
const install = this.installCommand(scratch.dir);
|
|
17195
|
+
this.deps.log(`dependencies changed; installing: ${install}`);
|
|
17196
|
+
await run(install, `the dependency install \`${install}\``);
|
|
17197
|
+
} else {
|
|
17198
|
+
this.deps.linkDependencies(worktreeDir, scratch.dir);
|
|
17199
|
+
}
|
|
17200
|
+
for (const check of checks) {
|
|
17201
|
+
this.deps.log(`check (${sha.slice(0, 7)}): ${check}`);
|
|
17202
|
+
await run(check, `\`${check}\``);
|
|
17203
|
+
}
|
|
17204
|
+
return checks;
|
|
17205
|
+
} finally {
|
|
17206
|
+
this.git(worktreeDir, "worktree", "remove", "--force", scratch.dir);
|
|
17207
|
+
scratch.cleanup();
|
|
17208
|
+
this.git(worktreeDir, "worktree", "prune");
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
17211
|
+
// ── recorded merges ───────────────────────────────────────────────
|
|
17212
|
+
recordedMerge(cwd) {
|
|
17213
|
+
return this.sha(cwd, `${MERGED_REFS}/${this.input.item}`);
|
|
17214
|
+
}
|
|
17215
|
+
forgetMerge(cwd) {
|
|
17216
|
+
this.git(cwd, "update-ref", "-d", `${MERGED_REFS}/${this.input.item}`);
|
|
17217
|
+
this.git(cwd, "update-ref", "-d", `${TESTED_REFS}/${this.input.item}`);
|
|
17218
|
+
}
|
|
17219
|
+
/**
|
|
17220
|
+
* The default branch's commits origin lacks, along its first parents, are pushed only when each is a merge this
|
|
17221
|
+
* command recorded; anything else on it was not merged here and is refused, so a local `git merge` is never
|
|
17222
|
+
* laundered through a push. Returns a note for the detail.
|
|
17223
|
+
*/
|
|
17224
|
+
pushRecordedOnly(mainCheckout, defaultBranch) {
|
|
17225
|
+
const remote = `refs/remotes/origin/${defaultBranch}`;
|
|
17226
|
+
const ahead = this.sha(mainCheckout, remote) ? this.git(
|
|
17227
|
+
mainCheckout,
|
|
17228
|
+
"rev-list",
|
|
17229
|
+
"--first-parent",
|
|
17230
|
+
`${remote}..refs/heads/${defaultBranch}`
|
|
17231
|
+
).stdout.split("\n").filter(Boolean) : [];
|
|
17232
|
+
if (ahead.length === 0) return "";
|
|
17233
|
+
const recorded = new Set(
|
|
17234
|
+
this.git(
|
|
17235
|
+
mainCheckout,
|
|
17236
|
+
"for-each-ref",
|
|
17237
|
+
"--format=%(objectname)",
|
|
17238
|
+
MERGED_REFS
|
|
17239
|
+
).stdout.split("\n").filter(Boolean)
|
|
17240
|
+
);
|
|
17241
|
+
const foreign = ahead.filter((commit) => !recorded.has(commit));
|
|
17242
|
+
if (foreign.length > 0)
|
|
17243
|
+
refuse(
|
|
17244
|
+
`${defaultBranch} has commits drisp drive merge did not make, so it pushes nothing; a member must remove them (or push them) first`,
|
|
17245
|
+
this.git(
|
|
17246
|
+
mainCheckout,
|
|
17247
|
+
"log",
|
|
17248
|
+
"--oneline",
|
|
17249
|
+
"--no-walk",
|
|
17250
|
+
...foreign
|
|
17251
|
+
).stdout.trim()
|
|
17252
|
+
);
|
|
17253
|
+
const pushed = this.git(
|
|
17254
|
+
mainCheckout,
|
|
17255
|
+
"push",
|
|
17256
|
+
"--quiet",
|
|
17257
|
+
"origin",
|
|
17258
|
+
defaultBranch
|
|
17259
|
+
);
|
|
17260
|
+
if (pushed.code !== 0)
|
|
17261
|
+
refuse(
|
|
17262
|
+
`${defaultBranch} holds a recorded merge that origin lacks, but pushing it failed; run the command again`,
|
|
17263
|
+
both(pushed)
|
|
17264
|
+
);
|
|
17265
|
+
return `; pushed ${defaultBranch} to origin`;
|
|
17266
|
+
}
|
|
17267
|
+
/** Fast-forward the default branch to origin's when origin moved on; refuse when they diverged. */
|
|
17268
|
+
catchUpWithOrigin(mainCheckout, defaultBranch) {
|
|
17269
|
+
this.must(mainCheckout, "git fetch origin", "fetch", "--quiet", "origin");
|
|
17270
|
+
const remote = `refs/remotes/origin/${defaultBranch}`;
|
|
17271
|
+
if (!this.sha(mainCheckout, remote)) return;
|
|
17272
|
+
if (this.isAncestor(mainCheckout, remote, `refs/heads/${defaultBranch}`))
|
|
17273
|
+
return;
|
|
17274
|
+
const ff = this.git(mainCheckout, "merge", "--ff-only", "--quiet", remote);
|
|
17275
|
+
if (ff.code !== 0)
|
|
17276
|
+
refuse(
|
|
17277
|
+
`${defaultBranch} and origin/${defaultBranch} have diverged`,
|
|
17278
|
+
both(ff)
|
|
17279
|
+
);
|
|
17280
|
+
}
|
|
17281
|
+
// ── a git origin, or none ─────────────────────────────────────────
|
|
17282
|
+
async viaGit(ctx) {
|
|
17283
|
+
const { worktreeDir, mainCheckout, branch, defaultBranch, hasOrigin } = ctx;
|
|
17284
|
+
const { item } = this.input;
|
|
17285
|
+
const defaultRef = `refs/heads/${defaultBranch}`;
|
|
17286
|
+
if (hasOrigin)
|
|
17287
|
+
this.must(mainCheckout, "git fetch origin", "fetch", "--quiet", "origin");
|
|
17288
|
+
const tip = this.must(
|
|
17289
|
+
worktreeDir,
|
|
17290
|
+
`reading ${branch}`,
|
|
17291
|
+
"rev-parse",
|
|
17292
|
+
`refs/heads/${branch}`
|
|
17293
|
+
);
|
|
17294
|
+
let recorded = this.recordedMerge(mainCheckout);
|
|
17295
|
+
if (recorded && !(this.isAncestor(mainCheckout, recorded, defaultRef) && (this.isAncestor(mainCheckout, tip, defaultRef) || this.sha(mainCheckout, `${TESTED_REFS}/${item}`) === tip))) {
|
|
17296
|
+
this.forgetMerge(mainCheckout);
|
|
17297
|
+
recorded = null;
|
|
17298
|
+
}
|
|
17299
|
+
if (recorded) {
|
|
17300
|
+
const pushed = hasOrigin ? this.pushRecordedOnly(mainCheckout, defaultBranch) : "";
|
|
17301
|
+
if (hasOrigin) this.deleteRemoteBranch(mainCheckout, branch);
|
|
17302
|
+
return {
|
|
17303
|
+
outcome: "already_merged",
|
|
17304
|
+
detail: `${item} was merged at ${recorded.slice(0, 7)}${pushed}`
|
|
17305
|
+
};
|
|
17306
|
+
}
|
|
17307
|
+
if (this.isAncestor(mainCheckout, tip, defaultRef))
|
|
17308
|
+
refuse(
|
|
17309
|
+
`no commits to merge: ${defaultBranch} already holds every commit of ${branch}, and drisp drive merge did not merge it (a merge it did not record does not count)`
|
|
17310
|
+
);
|
|
17311
|
+
const checkedOut = this.checkedOut(mainCheckout);
|
|
17312
|
+
if (checkedOut !== defaultBranch)
|
|
17313
|
+
refuse(
|
|
17314
|
+
`the main checkout has ${checkedOut || "a detached HEAD"} checked out, not ${defaultBranch}`
|
|
17315
|
+
);
|
|
17316
|
+
const dirty = this.status(mainCheckout, "no");
|
|
17317
|
+
if (dirty.length > 0)
|
|
17318
|
+
refuse(
|
|
17319
|
+
"the main checkout has uncommitted changes to tracked files",
|
|
17320
|
+
dirty.join("\n")
|
|
17321
|
+
);
|
|
17322
|
+
for (let round = 1; round <= MAX_ROUNDS; round++) {
|
|
17323
|
+
if (hasOrigin) {
|
|
17324
|
+
this.pushRecordedOnly(mainCheckout, defaultBranch);
|
|
17325
|
+
this.catchUpWithOrigin(mainCheckout, defaultBranch);
|
|
17326
|
+
}
|
|
17327
|
+
const before = this.must(
|
|
17328
|
+
mainCheckout,
|
|
17329
|
+
"reading the base",
|
|
17330
|
+
"rev-parse",
|
|
17331
|
+
defaultRef
|
|
17332
|
+
);
|
|
17333
|
+
const sha = this.rebase(worktreeDir, defaultRef);
|
|
17334
|
+
const checks = await this.runRepoChecks(
|
|
17335
|
+
mainCheckout,
|
|
17336
|
+
worktreeDir,
|
|
17337
|
+
before,
|
|
17338
|
+
sha
|
|
17339
|
+
);
|
|
17340
|
+
if (hasOrigin) this.catchUpWithOrigin(mainCheckout, defaultBranch);
|
|
17341
|
+
const base = this.must(
|
|
17342
|
+
mainCheckout,
|
|
17343
|
+
"reading the base",
|
|
17344
|
+
"rev-parse",
|
|
17345
|
+
defaultRef
|
|
17346
|
+
);
|
|
17347
|
+
if (!this.isAncestor(mainCheckout, base, sha)) {
|
|
17348
|
+
this.deps.log(
|
|
17349
|
+
`${defaultBranch} moved to ${base.slice(0, 7)} during the checks; rebasing again`
|
|
17350
|
+
);
|
|
17351
|
+
continue;
|
|
17352
|
+
}
|
|
17353
|
+
const merge = this.must(
|
|
17354
|
+
mainCheckout,
|
|
17355
|
+
"creating the merge commit",
|
|
17356
|
+
"commit-tree",
|
|
17357
|
+
`${sha}^{tree}`,
|
|
17358
|
+
"-p",
|
|
17359
|
+
base,
|
|
17360
|
+
"-p",
|
|
17361
|
+
sha,
|
|
17362
|
+
"-m",
|
|
17363
|
+
`Merge ${item} (${branch})`
|
|
17364
|
+
);
|
|
17365
|
+
this.must(
|
|
17366
|
+
mainCheckout,
|
|
17367
|
+
"recording the merge",
|
|
17368
|
+
"update-ref",
|
|
17369
|
+
`${MERGED_REFS}/${item}`,
|
|
17370
|
+
merge
|
|
17371
|
+
);
|
|
17372
|
+
this.must(
|
|
17373
|
+
mainCheckout,
|
|
17374
|
+
"recording the merge",
|
|
17375
|
+
"update-ref",
|
|
17376
|
+
`${TESTED_REFS}/${item}`,
|
|
17377
|
+
sha
|
|
17378
|
+
);
|
|
17379
|
+
const advanced = this.advance(
|
|
17380
|
+
ctx,
|
|
17381
|
+
merge,
|
|
17382
|
+
` after ${checks.map((check) => `\`${check}\``).join(", ")} passed on ${sha.slice(0, 7)}`
|
|
17383
|
+
);
|
|
17384
|
+
if (advanced) return advanced;
|
|
17385
|
+
}
|
|
17386
|
+
return stop("checks_failed", baseKeptMoving(defaultBranch));
|
|
17387
|
+
}
|
|
17388
|
+
/**
|
|
17389
|
+
* Fast-forward the default branch to the recorded merge and push it. Returns null when origin moved under the
|
|
17390
|
+
* push, after undoing the local merge, so the caller rebases and checks again.
|
|
17391
|
+
*/
|
|
17392
|
+
advance(ctx, merge, evidence) {
|
|
17393
|
+
const { mainCheckout, branch, defaultBranch, hasOrigin } = ctx;
|
|
17394
|
+
const before = this.must(
|
|
17395
|
+
mainCheckout,
|
|
17396
|
+
"reading the base",
|
|
17397
|
+
"rev-parse",
|
|
17398
|
+
`refs/heads/${defaultBranch}`
|
|
17399
|
+
);
|
|
17400
|
+
const ff = this.git(mainCheckout, "merge", "--ff-only", "--quiet", merge);
|
|
17401
|
+
if (ff.code !== 0) {
|
|
17402
|
+
this.forgetMerge(mainCheckout);
|
|
17403
|
+
refuse(
|
|
17404
|
+
`fast-forwarding ${defaultBranch} in the main checkout failed`,
|
|
17405
|
+
both(ff)
|
|
17406
|
+
);
|
|
17407
|
+
}
|
|
17408
|
+
let note = "";
|
|
17409
|
+
if (hasOrigin) {
|
|
17410
|
+
const pushed = this.git(
|
|
17411
|
+
mainCheckout,
|
|
17412
|
+
"push",
|
|
17413
|
+
"--quiet",
|
|
17414
|
+
"origin",
|
|
17415
|
+
defaultBranch
|
|
17416
|
+
);
|
|
17417
|
+
if (pushed.code !== 0) {
|
|
17418
|
+
this.git(mainCheckout, "fetch", "--quiet", "origin");
|
|
17419
|
+
const remote = this.sha(
|
|
17420
|
+
mainCheckout,
|
|
17421
|
+
`refs/remotes/origin/${defaultBranch}`
|
|
17422
|
+
);
|
|
17423
|
+
if (remote && !this.isAncestor(mainCheckout, remote, merge)) {
|
|
17424
|
+
this.git(mainCheckout, "reset", "--keep", "--quiet", before);
|
|
17425
|
+
this.forgetMerge(mainCheckout);
|
|
17426
|
+
this.deps.log(
|
|
17427
|
+
`origin/${defaultBranch} moved during the merge; rebasing again`
|
|
17428
|
+
);
|
|
17429
|
+
return null;
|
|
17430
|
+
}
|
|
17431
|
+
refuse(
|
|
17432
|
+
`merged ${branch} into ${defaultBranch} locally (recorded), but pushing ${defaultBranch} failed; run the command again to push`,
|
|
17433
|
+
both(pushed)
|
|
17434
|
+
);
|
|
17435
|
+
}
|
|
17436
|
+
this.deleteRemoteBranch(mainCheckout, branch);
|
|
17437
|
+
note = "; pushed to origin";
|
|
17438
|
+
}
|
|
17439
|
+
return {
|
|
17440
|
+
outcome: "merged",
|
|
17441
|
+
detail: `${branch} merged into ${defaultBranch} at ${merge.slice(0, 7)}${evidence}${note}`
|
|
17442
|
+
};
|
|
17443
|
+
}
|
|
17444
|
+
// ── GitHub ────────────────────────────────────────────────────────
|
|
17445
|
+
async viewPullRequest(worktreeDir, selector) {
|
|
17446
|
+
const view = await this.deps.gh(worktreeDir, [
|
|
17447
|
+
"pr",
|
|
17448
|
+
"view",
|
|
17449
|
+
selector,
|
|
17450
|
+
"--json",
|
|
17451
|
+
"number,state,url,headRefName,headRefOid,baseRefName,mergedAt"
|
|
17452
|
+
]);
|
|
17453
|
+
if (view.code !== 0)
|
|
17454
|
+
refuse(
|
|
17455
|
+
`no pull request found for ${selector}; the subagent opens one before the merge`,
|
|
17456
|
+
both(view)
|
|
17457
|
+
);
|
|
17458
|
+
try {
|
|
17459
|
+
return JSON.parse(view.stdout);
|
|
17460
|
+
} catch {
|
|
17461
|
+
return refuse("could not read gh pr view", both(view));
|
|
17462
|
+
}
|
|
17463
|
+
}
|
|
17464
|
+
async viaGitHub(ctx) {
|
|
17465
|
+
const { worktreeDir, mainCheckout, branch, defaultBranch } = ctx;
|
|
17466
|
+
let pr = await this.viewPullRequest(worktreeDir, this.input.pr ?? branch);
|
|
17467
|
+
if (pr.headRefName !== branch)
|
|
17468
|
+
refuse(
|
|
17469
|
+
`pull request #${pr.number} is for ${pr.headRefName}, not ${branch}`
|
|
17470
|
+
);
|
|
17471
|
+
if (pr.state === "MERGED") {
|
|
17472
|
+
this.pullMainCheckout(mainCheckout, defaultBranch);
|
|
17473
|
+
this.deleteRemoteBranch(mainCheckout, branch);
|
|
17474
|
+
return {
|
|
17475
|
+
outcome: "already_merged",
|
|
17476
|
+
detail: `pull request #${pr.number} was merged at ${pr.mergedAt ?? "an earlier time"}`
|
|
17477
|
+
};
|
|
17478
|
+
}
|
|
17479
|
+
if (pr.state !== "OPEN")
|
|
17480
|
+
refuse(`pull request #${pr.number} is ${pr.state.toLowerCase()}`);
|
|
17481
|
+
if (pr.baseRefName !== defaultBranch)
|
|
17482
|
+
refuse(
|
|
17483
|
+
`pull request #${pr.number} targets ${pr.baseRefName}, not ${defaultBranch}`
|
|
17484
|
+
);
|
|
17485
|
+
const base = `refs/remotes/origin/${defaultBranch}`;
|
|
17486
|
+
const fetchBase = () => this.must(
|
|
17487
|
+
worktreeDir,
|
|
17488
|
+
"git fetch origin",
|
|
17489
|
+
"fetch",
|
|
17490
|
+
"--quiet",
|
|
17491
|
+
"origin",
|
|
17492
|
+
defaultBranch
|
|
17493
|
+
);
|
|
17494
|
+
fetchBase();
|
|
17495
|
+
if (this.isAncestor(worktreeDir, `refs/heads/${branch}`, base))
|
|
17496
|
+
refuse(
|
|
17497
|
+
`no commits to merge: origin/${defaultBranch} already holds every commit of ${branch}`
|
|
17498
|
+
);
|
|
17499
|
+
for (let round = 1; round <= MAX_ROUNDS; round++) {
|
|
17500
|
+
if (round > 1) {
|
|
17501
|
+
pr = await this.viewPullRequest(worktreeDir, String(pr.number));
|
|
17502
|
+
fetchBase();
|
|
17503
|
+
}
|
|
17504
|
+
const baseSha = this.must(
|
|
17505
|
+
worktreeDir,
|
|
17506
|
+
"reading the base",
|
|
17507
|
+
"rev-parse",
|
|
17508
|
+
base
|
|
17509
|
+
);
|
|
17510
|
+
const sha = this.rebase(worktreeDir, base);
|
|
17511
|
+
this.pushBranch(worktreeDir, branch, pr.headRefOid, sha);
|
|
17512
|
+
const proof = await this.awaitChecks(worktreeDir, pr.number, sha);
|
|
17513
|
+
const ran = proof === "none" ? await this.runRepoChecks(mainCheckout, worktreeDir, baseSha, sha) : null;
|
|
17514
|
+
fetchBase();
|
|
17515
|
+
if (!this.isAncestor(worktreeDir, base, sha)) {
|
|
17516
|
+
this.deps.log(
|
|
17517
|
+
`origin/${defaultBranch} moved during the checks; rebasing again`
|
|
17518
|
+
);
|
|
17519
|
+
continue;
|
|
17520
|
+
}
|
|
17521
|
+
const merged = await this.deps.gh(worktreeDir, [
|
|
17522
|
+
"pr",
|
|
17523
|
+
"merge",
|
|
17524
|
+
String(pr.number),
|
|
17525
|
+
"--squash",
|
|
17526
|
+
"--match-head-commit",
|
|
17527
|
+
sha
|
|
17528
|
+
]);
|
|
17529
|
+
if (merged.code !== 0) {
|
|
17530
|
+
const after = await this.viewPullRequest(
|
|
17531
|
+
worktreeDir,
|
|
17532
|
+
String(pr.number)
|
|
17533
|
+
);
|
|
17534
|
+
if (after.state !== "MERGED")
|
|
17535
|
+
refuse(`gh pr merge #${pr.number} failed`, both(merged));
|
|
17536
|
+
}
|
|
17537
|
+
this.pullMainCheckout(mainCheckout, defaultBranch);
|
|
17538
|
+
this.deleteRemoteBranch(mainCheckout, branch);
|
|
17539
|
+
return {
|
|
17540
|
+
outcome: "merged",
|
|
17541
|
+
detail: `pull request #${pr.number} squash-merged at ${sha.slice(0, 7)} after ${ran ? `no GitHub checks; ${ran.map((check) => `\`${check}\``).join(", ")} passed` : proof}`
|
|
17542
|
+
};
|
|
17543
|
+
}
|
|
17544
|
+
return stop("checks_failed", baseKeptMoving(defaultBranch));
|
|
17545
|
+
}
|
|
17546
|
+
/**
|
|
17547
|
+
* Push the rebased branch, leased on the head GitHub reported, so a commit pushed meanwhile is never overwritten.
|
|
17548
|
+
* When the lease fails it reads origin again: already at `sha` is fine; anything else is `refused`.
|
|
17549
|
+
*/
|
|
17550
|
+
pushBranch(worktreeDir, branch, lease, sha) {
|
|
17551
|
+
const pushed = this.git(
|
|
17552
|
+
worktreeDir,
|
|
17553
|
+
"push",
|
|
17554
|
+
"--quiet",
|
|
17555
|
+
`--force-with-lease=refs/heads/${branch}:${lease}`,
|
|
17556
|
+
"origin",
|
|
17557
|
+
`${sha}:refs/heads/${branch}`
|
|
17558
|
+
);
|
|
17559
|
+
if (pushed.code === 0) return;
|
|
17560
|
+
this.git(worktreeDir, "fetch", "--quiet", "origin", branch);
|
|
17561
|
+
const remote = this.sha(worktreeDir, `refs/remotes/origin/${branch}`);
|
|
17562
|
+
if (remote === sha) return;
|
|
17563
|
+
refuse(
|
|
17564
|
+
`origin's ${branch} is at ${remote?.slice(0, 7) ?? "an unknown commit"}, not the ${lease.slice(0, 7)} the pull request reported, so the push was refused; the subagent pulls those commits into its worktree, then merge again`,
|
|
17565
|
+
both(pushed)
|
|
17566
|
+
);
|
|
17567
|
+
}
|
|
17568
|
+
/**
|
|
17569
|
+
* Wait until GitHub reports the pushed commit's checks done. Required checks decide; with none required, all
|
|
17570
|
+
* checks, which must hold for a settle period after GitHub first reports the commit; with no checks at all
|
|
17571
|
+
* after the grace period (also counted from then), `none`. A failed or cancelled check is `checks_failed`; the
|
|
17572
|
+
* bound is `checks_timeout`.
|
|
17573
|
+
*/
|
|
17574
|
+
async awaitChecks(worktreeDir, pr, sha) {
|
|
17575
|
+
let seenAt = null;
|
|
17576
|
+
for (; ; ) {
|
|
17577
|
+
const view = await this.viewPullRequest(worktreeDir, String(pr));
|
|
17578
|
+
let verdict;
|
|
17579
|
+
if (view.headRefOid !== sha) {
|
|
17580
|
+
verdict = {
|
|
17581
|
+
kind: "waiting",
|
|
17582
|
+
detail: `GitHub has not seen ${sha.slice(0, 7)} yet`
|
|
17583
|
+
};
|
|
17584
|
+
} else {
|
|
17585
|
+
seenAt ??= this.deps.now();
|
|
17586
|
+
const since = this.deps.now() - seenAt;
|
|
17587
|
+
verdict = await this.readChecks(worktreeDir, pr);
|
|
17588
|
+
if (verdict.kind === "none" && since >= this.graceMs) return "none";
|
|
17589
|
+
if (verdict.kind === "passed" && verdict.scope === "checks" && since < this.settleMs)
|
|
17590
|
+
verdict = {
|
|
17591
|
+
kind: "waiting",
|
|
17592
|
+
detail: "checks passed; waiting for late checks to register"
|
|
17593
|
+
};
|
|
17594
|
+
}
|
|
17595
|
+
if (verdict.kind === "passed") return verdict.detail;
|
|
17596
|
+
if (verdict.kind === "failed") stop("checks_failed", verdict.detail);
|
|
17597
|
+
if (this.left() < this.pollMs)
|
|
17598
|
+
stop(
|
|
17599
|
+
"checks_timeout",
|
|
17600
|
+
`the command's ${Math.round(this.timeoutMs / 1e3)} s deadline passed waiting for checks (${verdict.detail}); run the command again to keep waiting`
|
|
17601
|
+
);
|
|
17602
|
+
this.deps.log(`waiting for checks: ${verdict.detail}`);
|
|
17603
|
+
await this.deps.sleep(this.pollMs);
|
|
17604
|
+
}
|
|
17605
|
+
}
|
|
17606
|
+
async readChecks(worktreeDir, pr) {
|
|
17607
|
+
const read = async (required) => {
|
|
17608
|
+
const result = await this.deps.gh(worktreeDir, [
|
|
17609
|
+
"pr",
|
|
17610
|
+
"checks",
|
|
17611
|
+
String(pr),
|
|
17612
|
+
...required ? ["--required"] : [],
|
|
17613
|
+
"--json",
|
|
17614
|
+
"name,bucket"
|
|
17615
|
+
]);
|
|
17616
|
+
return readCheckList(result.stdout, result.stderr);
|
|
17617
|
+
};
|
|
17618
|
+
const empty = (list2) => list2.kind === "none" || list2.kind === "checks" && list2.checks.length === 0;
|
|
17619
|
+
let list = await read(true);
|
|
17620
|
+
let scope = "required checks";
|
|
17621
|
+
if (empty(list)) {
|
|
17622
|
+
list = await read(false);
|
|
17623
|
+
scope = "checks";
|
|
17624
|
+
}
|
|
17625
|
+
if (empty(list)) return { kind: "none", detail: "no checks reported" };
|
|
17626
|
+
if (list.kind === "unreadable")
|
|
17627
|
+
return { kind: "waiting", detail: list.detail };
|
|
17628
|
+
return list.kind === "checks" ? judge(list.checks, scope) : { kind: "none", detail: "no checks reported" };
|
|
17629
|
+
}
|
|
17630
|
+
/**
|
|
17631
|
+
* Delete the merged item's branch on origin, so clean-up needs no push of its own (in Run D of the sandbox a
|
|
17632
|
+
* clean-up loop's `git push origin --delete $b` parked the Run: its target is a variable). Best effort: a branch
|
|
17633
|
+
* that was never pushed, or one GitHub already deleted, is fine.
|
|
17634
|
+
*/
|
|
17635
|
+
deleteRemoteBranch(mainCheckout, branch) {
|
|
17636
|
+
if (this.git(
|
|
17637
|
+
mainCheckout,
|
|
17638
|
+
"ls-remote",
|
|
17639
|
+
"--exit-code",
|
|
17640
|
+
"--heads",
|
|
17641
|
+
"origin",
|
|
17642
|
+
branch
|
|
17643
|
+
).code !== 0)
|
|
17644
|
+
return;
|
|
17645
|
+
this.git(mainCheckout, "push", "--quiet", "origin", "--delete", branch);
|
|
17646
|
+
}
|
|
17647
|
+
/** Bring the main checkout up to date after a merge on GitHub, so the next worktree starts from it. Best effort. */
|
|
17648
|
+
pullMainCheckout(mainCheckout, defaultBranch) {
|
|
17649
|
+
if (this.checkedOut(mainCheckout) !== defaultBranch) return;
|
|
17650
|
+
this.git(
|
|
17651
|
+
mainCheckout,
|
|
17652
|
+
"pull",
|
|
17653
|
+
"--quiet",
|
|
17654
|
+
"--ff-only",
|
|
17655
|
+
"origin",
|
|
17656
|
+
defaultBranch
|
|
17657
|
+
);
|
|
17658
|
+
}
|
|
17659
|
+
};
|
|
17660
|
+
|
|
17661
|
+
// src/core/drive/processes.ts
|
|
17662
|
+
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
17663
|
+
import fs13 from "fs";
|
|
17664
|
+
import os4 from "os";
|
|
17665
|
+
import path13 from "path";
|
|
17666
|
+
|
|
17667
|
+
// src/core/drive/lock.ts
|
|
17668
|
+
import fs12 from "fs";
|
|
17669
|
+
import path12 from "path";
|
|
17670
|
+
var LOCK_FILE = "drisp-drive-merge.lock";
|
|
17671
|
+
function alive(pid) {
|
|
17672
|
+
try {
|
|
17673
|
+
process.kill(pid, 0);
|
|
17674
|
+
return true;
|
|
17675
|
+
} catch (error) {
|
|
17676
|
+
return error.code === "EPERM";
|
|
17677
|
+
}
|
|
17678
|
+
}
|
|
17679
|
+
var TAKEOVER_STALE_MS = 1e4;
|
|
17680
|
+
function ownerOf(file) {
|
|
17681
|
+
try {
|
|
17682
|
+
const owner = Number(fs12.readFileSync(file, "utf8"));
|
|
17683
|
+
return Number.isInteger(owner) && owner > 0 ? owner : null;
|
|
17684
|
+
} catch {
|
|
17685
|
+
return null;
|
|
17686
|
+
}
|
|
17687
|
+
}
|
|
17688
|
+
function takeOverStale(file, deadOwner) {
|
|
17689
|
+
const marker = `${file}.takeover`;
|
|
17690
|
+
try {
|
|
17691
|
+
fs12.writeFileSync(marker, String(process.pid), { flag: "wx" });
|
|
17692
|
+
} catch {
|
|
17693
|
+
try {
|
|
17694
|
+
if (Date.now() - fs12.statSync(marker).mtimeMs > TAKEOVER_STALE_MS)
|
|
17695
|
+
fs12.unlinkSync(marker);
|
|
17696
|
+
} catch {
|
|
17697
|
+
}
|
|
17698
|
+
return;
|
|
17699
|
+
}
|
|
17700
|
+
try {
|
|
17701
|
+
if (ownerOf(file) === deadOwner) fs12.unlinkSync(file);
|
|
17702
|
+
} catch {
|
|
17703
|
+
} finally {
|
|
17704
|
+
fs12.unlinkSync(marker);
|
|
17705
|
+
}
|
|
17706
|
+
}
|
|
17707
|
+
async function acquireLock(commonDir, waitMs) {
|
|
17708
|
+
const file = path12.join(commonDir, LOCK_FILE);
|
|
17709
|
+
const deadline = Date.now() + waitMs;
|
|
17710
|
+
for (; ; ) {
|
|
17711
|
+
try {
|
|
17712
|
+
fs12.writeFileSync(file, String(process.pid), { flag: "wx" });
|
|
17713
|
+
return () => {
|
|
17714
|
+
if (ownerOf(file) === process.pid) {
|
|
17715
|
+
try {
|
|
17716
|
+
fs12.unlinkSync(file);
|
|
17717
|
+
} catch {
|
|
17718
|
+
}
|
|
17719
|
+
}
|
|
17720
|
+
};
|
|
17721
|
+
} catch (error) {
|
|
17722
|
+
if (error.code !== "EEXIST") throw error;
|
|
17723
|
+
}
|
|
17724
|
+
const owner = ownerOf(file);
|
|
17725
|
+
if (owner !== null && !alive(owner)) {
|
|
17726
|
+
takeOverStale(file, owner);
|
|
17727
|
+
continue;
|
|
17728
|
+
}
|
|
17729
|
+
if (Date.now() >= deadline) return null;
|
|
17730
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
17731
|
+
}
|
|
17732
|
+
}
|
|
17733
|
+
|
|
17734
|
+
// src/core/drive/processes.ts
|
|
17735
|
+
var OUTPUT_CAP = 1024 * 1024;
|
|
17736
|
+
function runGit(cwd, args) {
|
|
17737
|
+
const result = spawnSync2("git", ["-C", cwd, ...args], {
|
|
17738
|
+
encoding: "utf8",
|
|
17739
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
17740
|
+
maxBuffer: 64 * 1024 * 1024
|
|
17741
|
+
});
|
|
17742
|
+
return {
|
|
17743
|
+
code: result.error ? 127 : result.status ?? 1,
|
|
17744
|
+
stdout: result.stdout,
|
|
17745
|
+
stderr: result.error ? result.error.message : result.stderr
|
|
17746
|
+
};
|
|
17747
|
+
}
|
|
17748
|
+
function runProcess(program, args, cwd, timeoutMs) {
|
|
17749
|
+
return new Promise((resolve) => {
|
|
17750
|
+
let stdout = "";
|
|
17751
|
+
let stderr = "";
|
|
17752
|
+
let timedOut = false;
|
|
17753
|
+
const child = spawn(program, args, {
|
|
17754
|
+
cwd,
|
|
17755
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
17756
|
+
detached: true
|
|
17757
|
+
});
|
|
17758
|
+
const keep = (text, chunk) => (text + chunk.toString()).slice(-OUTPUT_CAP);
|
|
17759
|
+
child.stdout.on("data", (chunk) => {
|
|
17760
|
+
stdout = keep(stdout, chunk);
|
|
17761
|
+
});
|
|
17762
|
+
child.stderr.on("data", (chunk) => {
|
|
17763
|
+
stderr = keep(stderr, chunk);
|
|
17764
|
+
});
|
|
17765
|
+
const timer = setTimeout(() => {
|
|
17766
|
+
timedOut = true;
|
|
17767
|
+
try {
|
|
17768
|
+
if (child.pid) process.kill(-child.pid, "SIGKILL");
|
|
17769
|
+
} catch {
|
|
17770
|
+
child.kill("SIGKILL");
|
|
17771
|
+
}
|
|
17772
|
+
}, timeoutMs);
|
|
17773
|
+
child.on("error", (error) => {
|
|
17774
|
+
clearTimeout(timer);
|
|
17775
|
+
resolve({ code: 127, stdout, stderr: error.message, timedOut });
|
|
17776
|
+
});
|
|
17777
|
+
child.on("close", (code, signal) => {
|
|
17778
|
+
clearTimeout(timer);
|
|
17779
|
+
resolve({
|
|
17780
|
+
code: code ?? (signal ? 128 : 1),
|
|
17781
|
+
stdout,
|
|
17782
|
+
stderr,
|
|
17783
|
+
timedOut
|
|
17784
|
+
});
|
|
17785
|
+
});
|
|
17786
|
+
});
|
|
17787
|
+
}
|
|
17788
|
+
var GH_TIMEOUT_MS = 6e4;
|
|
17789
|
+
function runCheck(cwd, command, timeoutMs) {
|
|
17790
|
+
return runProcess(
|
|
17791
|
+
"bash",
|
|
17792
|
+
["-e", "-o", "pipefail", "-c", command],
|
|
17793
|
+
cwd,
|
|
17794
|
+
timeoutMs
|
|
17795
|
+
).then((result) => ({
|
|
17796
|
+
...result,
|
|
17797
|
+
// One stream reads better in a Report: the check's own interleaving is lost either way.
|
|
17798
|
+
stdout: [result.stdout, result.stderr].filter(Boolean).join("\n"),
|
|
17799
|
+
stderr: ""
|
|
17800
|
+
}));
|
|
17801
|
+
}
|
|
17802
|
+
function packageTestScript(dir) {
|
|
17803
|
+
try {
|
|
17804
|
+
const manifest = JSON.parse(
|
|
17805
|
+
fs13.readFileSync(path13.join(dir, "package.json"), "utf8")
|
|
17806
|
+
);
|
|
17807
|
+
const test = manifest.scripts?.["test"];
|
|
17808
|
+
return typeof test === "string" && test.trim() ? test : void 0;
|
|
17809
|
+
} catch {
|
|
17810
|
+
return void 0;
|
|
17811
|
+
}
|
|
17812
|
+
}
|
|
17813
|
+
function scratchDir() {
|
|
17814
|
+
const parent = fs13.mkdtempSync(path13.join(os4.tmpdir(), SCRATCH_PREFIX));
|
|
17815
|
+
return {
|
|
17816
|
+
dir: path13.join(parent, "checkout"),
|
|
17817
|
+
cleanup: () => fs13.rmSync(parent, { recursive: true, force: true })
|
|
17818
|
+
};
|
|
17819
|
+
}
|
|
17820
|
+
function linkDependencies(fromDir, toDir) {
|
|
17821
|
+
const modules = path13.join(fromDir, "node_modules");
|
|
17822
|
+
if (fs13.existsSync(modules) && !fs13.existsSync(path13.join(toDir, "node_modules")))
|
|
17823
|
+
fs13.symlinkSync(modules, path13.join(toDir, "node_modules"), "dir");
|
|
17824
|
+
}
|
|
17825
|
+
function processDeps(configuredChecks, log) {
|
|
17826
|
+
return {
|
|
17827
|
+
git: runGit,
|
|
17828
|
+
gh: (cwd, args) => runProcess("gh", args, cwd, GH_TIMEOUT_MS).then(
|
|
17829
|
+
({ timedOut, ...result }) => timedOut ? {
|
|
17830
|
+
...result,
|
|
17831
|
+
code: result.code || 124,
|
|
17832
|
+
stderr: `${result.stderr}
|
|
17833
|
+
gh timed out`
|
|
17834
|
+
} : result
|
|
17835
|
+
),
|
|
17836
|
+
runCheck,
|
|
17837
|
+
configuredChecks,
|
|
17838
|
+
packageTestScript,
|
|
17839
|
+
lock: acquireLock,
|
|
17840
|
+
scratchDir,
|
|
17841
|
+
linkDependencies,
|
|
17842
|
+
exists: (target) => fs13.existsSync(target),
|
|
17843
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
17844
|
+
now: () => Date.now(),
|
|
17845
|
+
log
|
|
17846
|
+
};
|
|
17847
|
+
}
|
|
17848
|
+
|
|
17849
|
+
// src/app/entry/driveCommand.ts
|
|
17850
|
+
var USAGE4 = `Usage: drisp drive merge <item> --worktree <path> [--branch <branch>] [--pr <number|url>] [--timeout-ms <ms>]
|
|
17851
|
+
Merges a drive item: already merged \u2192 already_merged; rebase onto the default branch (conflict); on GitHub push with
|
|
17852
|
+
--force-with-lease, wait for the required checks (all checks when none is required; the repository's checks when
|
|
17853
|
+
there are none) and gh pr merge; otherwise run the repository's checks (.athena/config.json "checks", else the
|
|
17854
|
+
package.json test script) and merge --no-ff into the default branch, then push it. Prints {outcome, detail, output?}
|
|
17855
|
+
with outcome merged | already_merged | conflict | checks_failed | checks_timeout | refused.`;
|
|
17856
|
+
async function runDriveCommand(input) {
|
|
17857
|
+
const stdout = input.stdout ?? process.stdout;
|
|
17858
|
+
const stderr = input.stderr ?? process.stderr;
|
|
17859
|
+
const [item, ...extra] = input.positional;
|
|
17860
|
+
if (input.subcommand !== "merge" || !item || extra.length > 0 || !input.flags.worktree || input.flags.timeoutMs !== void 0 && !(Number.isFinite(input.flags.timeoutMs) && input.flags.timeoutMs > 0)) {
|
|
17861
|
+
stderr.write(`${USAGE4}
|
|
17862
|
+
`);
|
|
17863
|
+
return 2;
|
|
17864
|
+
}
|
|
17865
|
+
const deps = input.deps ?? processDeps(
|
|
17866
|
+
(dir) => readConfig(dir).checks,
|
|
17867
|
+
(line) => stderr.write(`${line}
|
|
17868
|
+
`)
|
|
17869
|
+
);
|
|
17870
|
+
let result;
|
|
17871
|
+
try {
|
|
17872
|
+
result = await driveMerge(
|
|
17873
|
+
{
|
|
17874
|
+
item,
|
|
17875
|
+
worktree: path14.resolve(input.cwd, input.flags.worktree),
|
|
17876
|
+
...input.flags.branch ? { branch: input.flags.branch } : {},
|
|
17877
|
+
...input.flags.pr ? { pr: input.flags.pr } : {},
|
|
17878
|
+
...input.flags.timeoutMs !== void 0 ? { timeoutMs: input.flags.timeoutMs } : {}
|
|
17879
|
+
},
|
|
17880
|
+
deps
|
|
17881
|
+
);
|
|
17882
|
+
} catch (error) {
|
|
17883
|
+
result = {
|
|
17884
|
+
outcome: "refused",
|
|
17885
|
+
detail: `drive merge failed unexpectedly: ${error instanceof Error ? error.message : String(error)}`
|
|
17886
|
+
};
|
|
17887
|
+
}
|
|
17888
|
+
stdout.write(`${JSON.stringify({ item, ...result })}
|
|
17889
|
+
`);
|
|
17890
|
+
return result.outcome === "merged" || result.outcome === "already_merged" ? 0 : 1;
|
|
17891
|
+
}
|
|
17892
|
+
|
|
17893
|
+
// src/app/entry/trackerCommand.ts
|
|
17894
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
17895
|
+
async function runTrackerCommand(args, env2 = process.env) {
|
|
17896
|
+
const usage2 = "Usage: drisp tracker get <issue-id> | apply <operation.json>\nOperations: issue.revision, issue.comment, issue.attention, issue.evidence. Read the Issue first for its current version. Evidence includes revision, reference, kind and runId; staging/QA also require deploymentId. Production release confirmation is a human action.";
|
|
17897
|
+
try {
|
|
17898
|
+
if (!env2.DRISP_HUB_URL || !env2.DRISP_RUN_TOKEN)
|
|
17899
|
+
throw new Error(
|
|
17900
|
+
"This command requires a native Issue Run with DRISP_HUB_URL and DRISP_RUN_TOKEN."
|
|
17901
|
+
);
|
|
17902
|
+
const client2 = DrispClient.delegated(
|
|
17903
|
+
env2.DRISP_HUB_URL,
|
|
17904
|
+
env2.DRISP_RUN_TOKEN
|
|
17905
|
+
);
|
|
17906
|
+
const [action, argument, ...extra] = args;
|
|
17907
|
+
if (!argument || extra.length) throw new Error(usage2);
|
|
17908
|
+
if (action === "get")
|
|
17909
|
+
process.stdout.write(
|
|
17910
|
+
JSON.stringify(await client2.trackerIssue(argument)) + "\n"
|
|
17911
|
+
);
|
|
17912
|
+
else if (action === "apply") {
|
|
17913
|
+
const operation = trackerOperationSchema.parse(
|
|
17914
|
+
JSON.parse(await readFile2(argument, "utf8"))
|
|
17915
|
+
);
|
|
17916
|
+
process.stdout.write(
|
|
17917
|
+
JSON.stringify(await client2.trackerOperation(operation)) + "\n"
|
|
17918
|
+
);
|
|
17919
|
+
} else throw new Error(usage2);
|
|
17920
|
+
return 0;
|
|
17921
|
+
} catch (error) {
|
|
17922
|
+
process.stderr.write(
|
|
17923
|
+
(error instanceof Error ? error.message : String(error)) + "\n"
|
|
17924
|
+
);
|
|
17925
|
+
return 2;
|
|
17926
|
+
}
|
|
17927
|
+
}
|
|
17928
|
+
|
|
17929
|
+
// src/app/entry/workCommand.ts
|
|
17930
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
17931
|
+
var WORK_USAGE = `Usage: drisp work authority
|
|
17932
|
+
drisp work issue <issue-id> | plan <plan-id>
|
|
17933
|
+
drisp work start issue|plan <id> [--activation <file.json>] [--command-id <id>]
|
|
17934
|
+
drisp work follow-up issue|plan|execution <id> "<text>" [--command-id <id>]
|
|
17935
|
+
drisp work inspect <execution-id>
|
|
17936
|
+
drisp work hold|unhold|pause|resume <execution-id> [--reason <text>] [--generation <n>] [--command-id <id>]
|
|
17937
|
+
drisp work cancel <execution-id> [--reason <text>] [--grace-ms <0-590000>] [--generation <n>] [--command-id <id>]
|
|
17938
|
+
drisp work revise <assignment-id> --changes <file.json> --reason <text> [--command-id <id>]
|
|
17939
|
+
drisp work reconcile <effect-id> succeeded|failed --evidence <file.json> --generation <n>
|
|
17940
|
+
drisp work recover <assignment-id> --reason <text> [--session auto|original|fresh] [--runner-id <id>]
|
|
17941
|
+
drisp work close <execution-id> cancelled|failed [--acknowledge <effect-id,...>] [--reason <text>]
|
|
17942
|
+
drisp work answer <question-id> "<text>" | --decision allow|deny [--command-id <id>]
|
|
17943
|
+
drisp work accept|reject <execution-id> <package-id> "<reason>" [--generation <n>]
|
|
17944
|
+
drisp work brief
|
|
17945
|
+
drisp work forward <question-id> "<note>"
|
|
17946
|
+
drisp work incorporate <assignment-id> <version> --generation <n> [--tool-use-id <id>]
|
|
17947
|
+
DRISP_HUB_URL is the Team's Hub base (https://<hub>/v2/orgs/<team>); DRISP_HUB_TOKEN a member's token, DRISP_RUN_TOKEN a
|
|
17948
|
+
coordinated Run's credential (brief, incorporate, forward, and answer as that Run). incorporate reads
|
|
17949
|
+
DRISP_TOOL_USE_ID when set; the Runner does not set it yet (DRI-353). A control prints "requested" until the
|
|
17950
|
+
Hub records it effective; a cancel reads requested, then the cancellation's state (stopping, waiting_on_children,
|
|
17951
|
+
effects_outstanding, runs_lost), and effective only once the execution closed as cancelled, with cleanStop as the Hub
|
|
17952
|
+
recorded it. Nothing here starts a legacy Build: under legacy execution authority starts and controls are
|
|
17953
|
+
refused (execution_authority_legacy) and the Build commands stay the legacy path.`;
|
|
17954
|
+
function workCommandFlags(flags) {
|
|
17955
|
+
const result = {};
|
|
17956
|
+
for (const name of [
|
|
17957
|
+
"reason",
|
|
17958
|
+
"activation",
|
|
17959
|
+
"commandId",
|
|
17960
|
+
"toolUseId",
|
|
17961
|
+
"decision",
|
|
17962
|
+
"changes",
|
|
17963
|
+
"evidence",
|
|
17964
|
+
"session",
|
|
17965
|
+
"runnerId",
|
|
17966
|
+
"acknowledge"
|
|
17967
|
+
])
|
|
17968
|
+
if (typeof flags[name] === "string") result[name] = flags[name];
|
|
17969
|
+
for (const name of ["generation", "graceMs"])
|
|
17970
|
+
if (typeof flags[name] === "number") result[name] = flags[name];
|
|
17971
|
+
return result;
|
|
17972
|
+
}
|
|
17973
|
+
var UsageError = class extends Error {
|
|
17974
|
+
};
|
|
17975
|
+
var UNSUPPORTED = {
|
|
17976
|
+
control: "execution.control",
|
|
17977
|
+
amend: "execution.amend",
|
|
17978
|
+
activate: "execution.activate",
|
|
17979
|
+
message: "message.send",
|
|
17980
|
+
"record-effect": "effect.record",
|
|
17981
|
+
"decide-child": "coordinator.decide",
|
|
17982
|
+
build: "legacy.build_start"
|
|
17983
|
+
};
|
|
17984
|
+
var RUN_CREDENTIAL = /* @__PURE__ */ new Set(["brief", "incorporate", "forward"]);
|
|
17985
|
+
async function runWorkCommand(args, flags = {}, env2 = process.env, io = {
|
|
17986
|
+
out: (line) => process.stdout.write(line + "\n"),
|
|
17987
|
+
err: (line) => process.stderr.write(line + "\n")
|
|
17988
|
+
}) {
|
|
17989
|
+
const [sub = "", ...rest] = args;
|
|
17990
|
+
try {
|
|
17991
|
+
if (!sub || sub === "help") throw new UsageError(WORK_USAGE);
|
|
17992
|
+
const asRun = RUN_CREDENTIAL.has(sub);
|
|
17993
|
+
const token = asRun ? env2.DRISP_RUN_TOKEN ?? env2.DRISP_HUB_TOKEN : env2.DRISP_HUB_TOKEN ?? env2.DRISP_RUN_TOKEN;
|
|
17994
|
+
if (!env2.DRISP_HUB_URL || !token)
|
|
17995
|
+
throw new UsageError(
|
|
17996
|
+
`drisp work needs DRISP_HUB_URL and ${asRun ? "DRISP_RUN_TOKEN" : "DRISP_HUB_TOKEN"}.`
|
|
17997
|
+
);
|
|
17998
|
+
if (flags.generation !== void 0 && !(Number.isInteger(flags.generation) && flags.generation > 0))
|
|
17999
|
+
throw new UsageError("--generation must be a positive whole number.");
|
|
18000
|
+
const team = /\/v2\/orgs\/([^/]+)\/?$/.exec(
|
|
18001
|
+
new URL(env2.DRISP_HUB_URL).pathname
|
|
18002
|
+
);
|
|
18003
|
+
if (!team)
|
|
18004
|
+
throw new UsageError(
|
|
18005
|
+
"DRISP_HUB_URL must be the Team base, https://<hub>/v2/orgs/<team>."
|
|
18006
|
+
);
|
|
18007
|
+
const client2 = DrispClient.delegated(env2.DRISP_HUB_URL, token);
|
|
18008
|
+
const teamId = decodeURIComponent(team[1]);
|
|
18009
|
+
const flows = new WorkFlows(client2, teamId);
|
|
18010
|
+
const result = await dispatch(
|
|
18011
|
+
flows,
|
|
18012
|
+
sub,
|
|
18013
|
+
rest,
|
|
18014
|
+
flags,
|
|
18015
|
+
env2,
|
|
18016
|
+
io.readFile ?? ((path17) => readFile3(path17, "utf8"))
|
|
18017
|
+
);
|
|
18018
|
+
io.out(JSON.stringify(result));
|
|
18019
|
+
return 0;
|
|
18020
|
+
} catch (error) {
|
|
18021
|
+
if (error instanceof UsageError) {
|
|
18022
|
+
io.err(error.message);
|
|
18023
|
+
return 2;
|
|
18024
|
+
}
|
|
18025
|
+
if (error instanceof WorkFlowError || error instanceof ClientError) {
|
|
18026
|
+
io.out(
|
|
18027
|
+
JSON.stringify({
|
|
18028
|
+
error: error.code,
|
|
18029
|
+
...error instanceof ClientError ? { status: error.status } : {},
|
|
18030
|
+
...error.details
|
|
18031
|
+
})
|
|
18032
|
+
);
|
|
18033
|
+
return 1;
|
|
18034
|
+
}
|
|
18035
|
+
io.out(
|
|
18036
|
+
JSON.stringify({
|
|
18037
|
+
error: "request_failed",
|
|
18038
|
+
message: error instanceof Error ? error.message : String(error)
|
|
18039
|
+
})
|
|
18040
|
+
);
|
|
18041
|
+
return 1;
|
|
18042
|
+
}
|
|
18043
|
+
}
|
|
18044
|
+
function need(values, count) {
|
|
18045
|
+
if (values.length !== count) throw new UsageError(WORK_USAGE);
|
|
18046
|
+
return values;
|
|
18047
|
+
}
|
|
18048
|
+
function scopeOf(kind) {
|
|
18049
|
+
if (kind !== "issue" && kind !== "plan") throw new UsageError(WORK_USAGE);
|
|
18050
|
+
return kind;
|
|
18051
|
+
}
|
|
18052
|
+
async function dispatch(flows, sub, rest, flags, env2, read) {
|
|
18053
|
+
const commandId = flags.commandId;
|
|
18054
|
+
switch (sub) {
|
|
18055
|
+
case "authority": {
|
|
18056
|
+
need(rest, 0);
|
|
18057
|
+
const path17 = await flows.path();
|
|
18058
|
+
return { path: path17, operations: await flows.operations() };
|
|
18059
|
+
}
|
|
18060
|
+
case "issue": {
|
|
18061
|
+
const [id] = need(rest, 1);
|
|
18062
|
+
return flows.readIssue(id);
|
|
18063
|
+
}
|
|
18064
|
+
case "plan": {
|
|
18065
|
+
const [id] = need(rest, 1);
|
|
18066
|
+
return flows.readPlan(id);
|
|
18067
|
+
}
|
|
18068
|
+
case "start": {
|
|
18069
|
+
const [kind, id] = need(rest, 2);
|
|
18070
|
+
const activation = flags.activation ? JSON.parse(await read(flags.activation)) : void 0;
|
|
18071
|
+
if (activation && typeof activation.runId !== "string")
|
|
18072
|
+
activation.runId = crypto.randomUUID();
|
|
18073
|
+
return flows.start(
|
|
18074
|
+
{ kind: scopeOf(kind), id },
|
|
18075
|
+
{
|
|
18076
|
+
...activation ? { activation } : {},
|
|
18077
|
+
...commandId ? { commandId } : {}
|
|
18078
|
+
}
|
|
18079
|
+
);
|
|
18080
|
+
}
|
|
18081
|
+
case "follow-up": {
|
|
18082
|
+
const [kind, id, text] = need(rest, 3);
|
|
18083
|
+
const target = kind === "execution" ? { kind: "execution", id } : { kind: scopeOf(kind), id };
|
|
18084
|
+
return flows.followUp(target, text, commandId);
|
|
18085
|
+
}
|
|
18086
|
+
case "inspect": {
|
|
18087
|
+
const [id] = need(rest, 1);
|
|
18088
|
+
return flows.inspect(id);
|
|
18089
|
+
}
|
|
18090
|
+
case "hold":
|
|
18091
|
+
case "unhold":
|
|
18092
|
+
case "pause":
|
|
18093
|
+
case "resume":
|
|
18094
|
+
case "cancel": {
|
|
18095
|
+
const [id] = need(rest, 1);
|
|
18096
|
+
if (sub === "cancel" && flags.graceMs !== void 0 && !(Number.isInteger(flags.graceMs) && flags.graceMs >= 0 && flags.graceMs <= maxClientCancelBoundaryGraceMs))
|
|
18097
|
+
throw new UsageError(
|
|
18098
|
+
`--grace-ms must be a whole number of milliseconds from 0 to ${maxClientCancelBoundaryGraceMs}.`
|
|
18099
|
+
);
|
|
18100
|
+
return flows.control(id, sub, {
|
|
18101
|
+
...flags.reason ? { reason: flags.reason } : {},
|
|
18102
|
+
...flags.generation !== void 0 ? { expectedGeneration: flags.generation } : {},
|
|
18103
|
+
...sub === "cancel" && flags.graceMs !== void 0 ? { boundaryGraceMs: flags.graceMs } : {},
|
|
18104
|
+
...commandId ? { commandId } : {}
|
|
18105
|
+
});
|
|
18106
|
+
}
|
|
18107
|
+
case "revise": {
|
|
18108
|
+
const [id] = need(rest, 1);
|
|
18109
|
+
if (!flags.changes || !flags.reason) throw new UsageError(WORK_USAGE);
|
|
18110
|
+
const changes = JSON.parse(await read(flags.changes));
|
|
18111
|
+
return flows.revise(id, changes, flags.reason, {
|
|
18112
|
+
...commandId ? { commandId } : {}
|
|
18113
|
+
});
|
|
18114
|
+
}
|
|
18115
|
+
case "reconcile": {
|
|
18116
|
+
const [id, outcome] = need(rest, 2);
|
|
18117
|
+
if (outcome !== "succeeded" && outcome !== "failed" || !flags.evidence || flags.generation === void 0)
|
|
18118
|
+
throw new UsageError(WORK_USAGE);
|
|
18119
|
+
const evidence = JSON.parse(await read(flags.evidence));
|
|
18120
|
+
return flows.reconcile(id, outcome, evidence, {
|
|
18121
|
+
expectedGeneration: flags.generation,
|
|
18122
|
+
...commandId ? { commandId } : {}
|
|
18123
|
+
});
|
|
18124
|
+
}
|
|
18125
|
+
case "recover": {
|
|
18126
|
+
const [id] = need(rest, 1);
|
|
18127
|
+
const session = flags.session ?? "auto";
|
|
18128
|
+
if (!flags.reason || session !== "auto" && session !== "original" && session !== "fresh")
|
|
18129
|
+
throw new UsageError(WORK_USAGE);
|
|
18130
|
+
return flows.recover(id, {
|
|
18131
|
+
reason: flags.reason,
|
|
18132
|
+
session,
|
|
18133
|
+
...flags.runnerId ? { runnerId: flags.runnerId } : {},
|
|
18134
|
+
...commandId ? { commandId } : {}
|
|
18135
|
+
});
|
|
18136
|
+
}
|
|
18137
|
+
case "close": {
|
|
18138
|
+
const [id, outcome] = need(rest, 2);
|
|
18139
|
+
if (outcome !== "cancelled" && outcome !== "failed")
|
|
18140
|
+
throw new UsageError(WORK_USAGE);
|
|
18141
|
+
return flows.close(id, outcome, {
|
|
18142
|
+
...flags.reason ? { reason: flags.reason } : {},
|
|
18143
|
+
...flags.acknowledge ? { acknowledge: flags.acknowledge.split(",").filter(Boolean) } : {},
|
|
18144
|
+
...flags.generation !== void 0 ? { expectedGeneration: flags.generation } : {},
|
|
18145
|
+
...commandId ? { commandId } : {}
|
|
18146
|
+
});
|
|
18147
|
+
}
|
|
18148
|
+
case "answer": {
|
|
18149
|
+
const [id, text] = rest;
|
|
18150
|
+
if (!id || rest.length > 2) throw new UsageError(WORK_USAGE);
|
|
18151
|
+
const decision = flags.decision;
|
|
18152
|
+
const answer = decision === "allow" || decision === "deny" ? { kind: "permission", decision } : text ? { kind: "text", text } : void 0;
|
|
18153
|
+
if (!answer || text && flags.decision) throw new UsageError(WORK_USAGE);
|
|
18154
|
+
return flows.answer(id, answer, {
|
|
18155
|
+
...commandId ? { commandId } : {},
|
|
18156
|
+
...env2.DRISP_HUB_TOKEN ? {} : { asRun: true }
|
|
18157
|
+
});
|
|
18158
|
+
}
|
|
18159
|
+
case "accept":
|
|
18160
|
+
case "reject": {
|
|
18161
|
+
const [id, packageId, reason] = need(rest, 3);
|
|
18162
|
+
return flows.decide(id, packageId, sub, reason, {
|
|
18163
|
+
...flags.generation !== void 0 ? { expectedGeneration: flags.generation } : {},
|
|
18164
|
+
...commandId ? { commandId } : {}
|
|
18165
|
+
});
|
|
18166
|
+
}
|
|
18167
|
+
case "brief":
|
|
18168
|
+
need(rest, 0);
|
|
18169
|
+
return flows.brief();
|
|
18170
|
+
case "forward": {
|
|
18171
|
+
const [id, note] = need(rest, 2);
|
|
18172
|
+
return flows.forward(id, note, commandId ? { commandId } : {});
|
|
18173
|
+
}
|
|
18174
|
+
case "incorporate": {
|
|
18175
|
+
const [id, version2] = need(rest, 2);
|
|
18176
|
+
if (flags.generation === void 0 || !/^[1-9][0-9]*$/.test(version2))
|
|
18177
|
+
throw new UsageError(WORK_USAGE);
|
|
18178
|
+
const toolUseId = flags.toolUseId ?? env2.DRISP_TOOL_USE_ID;
|
|
18179
|
+
return flows.incorporate(id, Number(version2), {
|
|
18180
|
+
generation: flags.generation,
|
|
18181
|
+
...toolUseId ? { toolUseId } : {},
|
|
18182
|
+
...commandId ? { commandId } : {}
|
|
18183
|
+
});
|
|
18184
|
+
}
|
|
18185
|
+
default: {
|
|
18186
|
+
const operation = UNSUPPORTED[sub];
|
|
18187
|
+
const reason = operation ? WorkFlows.unsupported(operation) : void 0;
|
|
18188
|
+
if (operation && reason)
|
|
18189
|
+
throw new WorkFlowError("unsupported_operation", { operation, reason });
|
|
18190
|
+
throw new UsageError(WORK_USAGE);
|
|
18191
|
+
}
|
|
18192
|
+
}
|
|
18193
|
+
}
|
|
18194
|
+
|
|
18195
|
+
// src/app/entry/doctorCommand.ts
|
|
18196
|
+
import process7 from "process";
|
|
18197
|
+
|
|
18198
|
+
// src/harnesses/claude/system/doctorEnvironment.ts
|
|
18199
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
18200
|
+
import * as fs14 from "fs";
|
|
18201
|
+
import * as os5 from "os";
|
|
18202
|
+
import * as path15 from "path";
|
|
18203
|
+
var MANAGED_POLICY_KEYS = [
|
|
15655
18204
|
"forceLoginMethod",
|
|
15656
18205
|
"forceLoginOrgUUID",
|
|
15657
18206
|
"allowManagedHooksOnly",
|
|
@@ -15748,14 +18297,14 @@ function tryRichAuth(claudeBinary) {
|
|
|
15748
18297
|
function collectEnvironment(options = {}) {
|
|
15749
18298
|
const platform2 = options.platform ?? process.platform;
|
|
15750
18299
|
const env2 = options.env ?? process.env;
|
|
15751
|
-
const homeDir = options.homeDir ??
|
|
18300
|
+
const homeDir = options.homeDir ?? os5.homedir();
|
|
15752
18301
|
const cwd = options.cwd ?? process.cwd();
|
|
15753
18302
|
const resolveBinary = options.resolveClaudeBinaryFn ?? resolveClaudeBinary;
|
|
15754
18303
|
const detectVersion = options.detectClaudeVersionFn ?? detectClaudeVersion;
|
|
15755
18304
|
const runAuthStatus = options.runClaudeAuthStatusFn ?? runClaudeAuthStatus;
|
|
15756
18305
|
const resolveForwarder = options.resolveHookForwarderCommandFn ?? resolveHookForwarderCommand;
|
|
15757
|
-
const statFn = options.statFn ?? ((p) =>
|
|
15758
|
-
const readFileFn = options.readFileFn ?? ((p) =>
|
|
18306
|
+
const statFn = options.statFn ?? ((p) => fs14.statSync(p));
|
|
18307
|
+
const readFileFn = options.readFileFn ?? ((p) => fs14.readFileSync(p, "utf8"));
|
|
15759
18308
|
const settingsPaths = resolveClaudeSettingsSurfacePaths(
|
|
15760
18309
|
homeDir,
|
|
15761
18310
|
cwd,
|
|
@@ -15800,7 +18349,7 @@ function collectEnvironment(options = {}) {
|
|
|
15800
18349
|
});
|
|
15801
18350
|
let globalConfigPresent = false;
|
|
15802
18351
|
try {
|
|
15803
|
-
statFn(
|
|
18352
|
+
statFn(path15.join(homeDir, ".claude.json"));
|
|
15804
18353
|
globalConfigPresent = true;
|
|
15805
18354
|
} catch {
|
|
15806
18355
|
globalConfigPresent = false;
|
|
@@ -16280,13 +18829,13 @@ function runRunsCommand(input) {
|
|
|
16280
18829
|
// src/app/entry/cli.tsx
|
|
16281
18830
|
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
16282
18831
|
function resolvePackageJsonPath(entryUrl) {
|
|
16283
|
-
let currentDir =
|
|
18832
|
+
let currentDir = path16.dirname(fileURLToPath(entryUrl));
|
|
16284
18833
|
for (; ; ) {
|
|
16285
|
-
const candidatePath =
|
|
16286
|
-
if (
|
|
18834
|
+
const candidatePath = path16.join(currentDir, "package.json");
|
|
18835
|
+
if (fs15.existsSync(candidatePath)) {
|
|
16287
18836
|
return candidatePath;
|
|
16288
18837
|
}
|
|
16289
|
-
const parentDir =
|
|
18838
|
+
const parentDir = path16.dirname(currentDir);
|
|
16290
18839
|
if (parentDir === currentDir) {
|
|
16291
18840
|
throw new Error("Could not locate package.json for CLI metadata.");
|
|
16292
18841
|
}
|
|
@@ -16297,6 +18846,7 @@ var require2 = createRequire(import.meta.url);
|
|
|
16297
18846
|
var { version } = require2(resolvePackageJsonPath(import.meta.url));
|
|
16298
18847
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
16299
18848
|
"tracker",
|
|
18849
|
+
"work",
|
|
16300
18850
|
"setup",
|
|
16301
18851
|
"sessions",
|
|
16302
18852
|
"resume",
|
|
@@ -16310,6 +18860,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
16310
18860
|
"marketplace",
|
|
16311
18861
|
"runner",
|
|
16312
18862
|
"build",
|
|
18863
|
+
"drive",
|
|
16313
18864
|
// Deprecated alias of `runner` (#188); removed in 0.7.0.
|
|
16314
18865
|
"dashboard",
|
|
16315
18866
|
"telemetry",
|
|
@@ -16336,7 +18887,7 @@ async function flushTelemetryOptOut(config) {
|
|
|
16336
18887
|
deviceId: deviceId2,
|
|
16337
18888
|
telemetryEnabled: config.telemetry,
|
|
16338
18889
|
appVersion: version,
|
|
16339
|
-
os: `${
|
|
18890
|
+
os: `${os6.platform()}-${os6.arch()}`
|
|
16340
18891
|
});
|
|
16341
18892
|
trackTelemetryOptedOut();
|
|
16342
18893
|
await shutdownTelemetry();
|
|
@@ -16367,6 +18918,11 @@ function printExecDryRunSummary(runtimeConfig, context) {
|
|
|
16367
18918
|
lines.push(
|
|
16368
18919
|
` model: ${runtimeConfig.modelName ?? "(default)"} [${runtimeConfig.settingsProvenance?.model ?? "resolved"}]`
|
|
16369
18920
|
);
|
|
18921
|
+
lines.push(
|
|
18922
|
+
` gates: ${Object.entries(runtimeConfig.gates).map(
|
|
18923
|
+
([gate, mode]) => `${gate}=${mode} [${runtimeConfig.settingsProvenance?.gates[gate] ?? "resolved"}]`
|
|
18924
|
+
).join(", ")}`
|
|
18925
|
+
);
|
|
16370
18926
|
lines.push(
|
|
16371
18927
|
` permission grace: ${runtimeConfig.permissionGraceMs}ms [${runtimeConfig.settingsProvenance?.permissionGraceMs ?? "resolved"}]`
|
|
16372
18928
|
);
|
|
@@ -16459,6 +19015,9 @@ var cli = meow(
|
|
|
16459
19015
|
build plan --input <path> Plan a Build inside an Orchestrator Run (prints one JSON object)
|
|
16460
19016
|
tracker get <issue> Read the current native Drisp Issue
|
|
16461
19017
|
tracker apply <json> Apply a scoped native Issue operation from a JSON file
|
|
19018
|
+
work <sub> Canonical Work and coordinated executions (authority, issue, plan, start,
|
|
19019
|
+
follow-up, inspect, hold, unhold, pause, resume, cancel, close, revise, reconcile, recover, answer, accept, reject,
|
|
19020
|
+
brief, forward, incorporate); drisp work help for usage
|
|
16462
19021
|
build children List this Build's children and their interruptions
|
|
16463
19022
|
build inspect <run> [cursor] Read a child's state and paged history
|
|
16464
19023
|
build guide <run> "<text>" Guide a child or answer its delegated technical question
|
|
@@ -16468,6 +19027,8 @@ var cli = meow(
|
|
|
16468
19027
|
build step <n> Report the Run's current step: writes the step run artifact ([--qa none|web|macos|both], --profile, --merge you, --sign-off <sha>, --complete --evidence, --verify)
|
|
16469
19028
|
build check <name> -- <cmd> Run a command and record its outcome: writes the checks run artifact
|
|
16470
19029
|
build merged Record the merged pull request: writes the pull_request run artifact
|
|
19030
|
+
drive merge <item> --worktree <path> [--branch <b>] [--pr <n>] [--timeout-ms <ms>]
|
|
19031
|
+
Merge a drive item: rebase, prove the checks, merge (prints one JSON outcome)
|
|
16471
19032
|
telemetry [action] Manage anonymous telemetry (enable/disable/status)
|
|
16472
19033
|
doctor Diagnose Claude headless setup (use with --harness=claude)
|
|
16473
19034
|
|
|
@@ -16488,6 +19049,9 @@ var cli = meow(
|
|
|
16488
19049
|
--steer Queue a human steer for the head of the next Turn's prompt (repeatable; run mode)
|
|
16489
19050
|
--answer allow | deny: answer the permission a parked run is waiting on; replayed
|
|
16490
19051
|
into the re-issued call on --continue (run mode)
|
|
19052
|
+
--gate <gate>=ask|auto for this run only (repeatable; run mode). Gates:
|
|
19053
|
+
planApproval, merge, budget, irreversible. Only tightens: =auto has an
|
|
19054
|
+
effect only where the configured floor is already auto (docs/human-gates.md)
|
|
16491
19055
|
--permission-grace-ms How long a permission no rule answers is held for an attached
|
|
16492
19056
|
hub before it is deferred and the run parks (default 60000; run mode)
|
|
16493
19057
|
--json Emit JSONL events to stdout (run mode)
|
|
@@ -16575,6 +19139,10 @@ var cli = meow(
|
|
|
16575
19139
|
answer: {
|
|
16576
19140
|
type: "string"
|
|
16577
19141
|
},
|
|
19142
|
+
gate: {
|
|
19143
|
+
type: "string",
|
|
19144
|
+
isMultiple: true
|
|
19145
|
+
},
|
|
16578
19146
|
permissionGraceMs: {
|
|
16579
19147
|
type: "number"
|
|
16580
19148
|
},
|
|
@@ -16606,6 +19174,22 @@ var cli = meow(
|
|
|
16606
19174
|
isMultiple: true
|
|
16607
19175
|
},
|
|
16608
19176
|
profile: { type: "string" },
|
|
19177
|
+
// drisp drive merge (DRI-368)
|
|
19178
|
+
worktree: { type: "string" },
|
|
19179
|
+
branch: { type: "string" },
|
|
19180
|
+
pr: { type: "string" },
|
|
19181
|
+
// drisp work (DRI-342)
|
|
19182
|
+
reason: { type: "string" },
|
|
19183
|
+
generation: { type: "number" },
|
|
19184
|
+
activation: { type: "string" },
|
|
19185
|
+
commandId: { type: "string" },
|
|
19186
|
+
graceMs: { type: "number" },
|
|
19187
|
+
toolUseId: { type: "string" },
|
|
19188
|
+
decision: { type: "string" },
|
|
19189
|
+
changes: { type: "string" },
|
|
19190
|
+
session: { type: "string" },
|
|
19191
|
+
runnerId: { type: "string" },
|
|
19192
|
+
acknowledge: { type: "string" },
|
|
16609
19193
|
testFirst: { type: "string" },
|
|
16610
19194
|
review: { type: "string" },
|
|
16611
19195
|
merge: { type: "string" },
|
|
@@ -16655,7 +19239,7 @@ var cli = meow(
|
|
|
16655
19239
|
}
|
|
16656
19240
|
);
|
|
16657
19241
|
async function main() {
|
|
16658
|
-
const projectDir =
|
|
19242
|
+
const projectDir = path16.resolve(cli.flags.projectDir);
|
|
16659
19243
|
const [command, ...commandArgs] = cli.input;
|
|
16660
19244
|
const isRunCommand = command === "run" || command === "exec";
|
|
16661
19245
|
if (command && !KNOWN_COMMANDS.has(command)) {
|
|
@@ -16709,7 +19293,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
16709
19293
|
await exitWith(1);
|
|
16710
19294
|
return;
|
|
16711
19295
|
}
|
|
16712
|
-
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-
|
|
19296
|
+
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-MVTO43MW.js");
|
|
16713
19297
|
const { waitUntilExit } = render(
|
|
16714
19298
|
/* @__PURE__ */ jsx24(
|
|
16715
19299
|
WorkflowInstallWizard,
|
|
@@ -16791,6 +19375,32 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
16791
19375
|
await exitWith(await runTrackerCommand(commandArgs));
|
|
16792
19376
|
return;
|
|
16793
19377
|
}
|
|
19378
|
+
if (command === "work") {
|
|
19379
|
+
await exitWith(
|
|
19380
|
+
await runWorkCommand(
|
|
19381
|
+
commandArgs,
|
|
19382
|
+
workCommandFlags(cli.flags)
|
|
19383
|
+
)
|
|
19384
|
+
);
|
|
19385
|
+
return;
|
|
19386
|
+
}
|
|
19387
|
+
if (command === "drive") {
|
|
19388
|
+
const [subcommand = "", ...positional] = commandArgs;
|
|
19389
|
+
await exitWith(
|
|
19390
|
+
await runDriveCommand({
|
|
19391
|
+
subcommand,
|
|
19392
|
+
positional,
|
|
19393
|
+
flags: {
|
|
19394
|
+
...typeof cli.flags.worktree === "string" ? { worktree: cli.flags.worktree } : {},
|
|
19395
|
+
...typeof cli.flags.branch === "string" ? { branch: cli.flags.branch } : {},
|
|
19396
|
+
...typeof cli.flags.pr === "string" ? { pr: cli.flags.pr } : {},
|
|
19397
|
+
...typeof cli.flags.timeoutMs === "number" ? { timeoutMs: cli.flags.timeoutMs } : {}
|
|
19398
|
+
},
|
|
19399
|
+
cwd: process.cwd()
|
|
19400
|
+
})
|
|
19401
|
+
);
|
|
19402
|
+
return;
|
|
19403
|
+
}
|
|
16794
19404
|
if (command === "build") {
|
|
16795
19405
|
const rawArgs = process.argv.slice(2);
|
|
16796
19406
|
const dashIdx = rawArgs.indexOf("--");
|
|
@@ -16893,8 +19503,8 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
16893
19503
|
const showSetup = isRunCommand ? false : shouldShowSetup({
|
|
16894
19504
|
cliInput: cli.input,
|
|
16895
19505
|
setupComplete: globalConfig.setupComplete,
|
|
16896
|
-
globalConfigExists:
|
|
16897
|
-
|
|
19506
|
+
globalConfigExists: fs15.existsSync(
|
|
19507
|
+
path16.join(os6.homedir(), ".config", "athena", "config.json")
|
|
16898
19508
|
)
|
|
16899
19509
|
});
|
|
16900
19510
|
let runtimeConfig;
|
|
@@ -16926,6 +19536,11 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
16926
19536
|
await exitWith(RUN_EXIT_CODE.USAGE);
|
|
16927
19537
|
return;
|
|
16928
19538
|
}
|
|
19539
|
+
if ((cli.flags.gate ?? []).length > 0 && !isRunCommand) {
|
|
19540
|
+
console.error("Error: --gate is only supported in run mode.");
|
|
19541
|
+
await exitWith(RUN_EXIT_CODE.USAGE);
|
|
19542
|
+
return;
|
|
19543
|
+
}
|
|
16929
19544
|
if (cli.flags.dryRun) {
|
|
16930
19545
|
if (!isRunCommand) {
|
|
16931
19546
|
console.error("Error: --dry-run is only supported in exec mode.");
|
|
@@ -16951,7 +19566,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
16951
19566
|
deviceId: resolvedDeviceId,
|
|
16952
19567
|
telemetryEnabled: globalConfig.telemetry,
|
|
16953
19568
|
appVersion: version,
|
|
16954
|
-
os: `${
|
|
19569
|
+
os: `${os6.platform()}-${os6.arch()}`
|
|
16955
19570
|
});
|
|
16956
19571
|
trackAppLaunched({ version, harness: runtimeConfig.harness });
|
|
16957
19572
|
if (!isRunCommand && isFirstRun && globalConfig.telemetry !== false) {
|
|
@@ -16972,7 +19587,8 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
16972
19587
|
timeoutMs: cli.flags.timeoutMs,
|
|
16973
19588
|
verbose: cli.flags.verbose,
|
|
16974
19589
|
permissionGraceMs: cli.flags.permissionGraceMs,
|
|
16975
|
-
answer: cli.flags.answer
|
|
19590
|
+
answer: cli.flags.answer,
|
|
19591
|
+
gates: cli.flags.gate
|
|
16976
19592
|
},
|
|
16977
19593
|
runtimeConfig
|
|
16978
19594
|
});
|