@useorgx/openclaw-plugin 0.7.0 → 0.7.3
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 +42 -11
- package/dashboard/dist/assets/6mILZQ2a.js +1 -0
- package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
- package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
- package/dashboard/dist/assets/{DG6y9wJI.js → 8dksYiq4.js} +1 -1
- package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
- package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
- package/dashboard/dist/assets/{PAUiij_z.js → B5zYRHc3.js} +1 -1
- package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
- package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
- package/dashboard/dist/assets/{CFGKRAzG.js → B6wPWJ35.js} +1 -1
- package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
- package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
- package/dashboard/dist/assets/BWEwjt1W.js +1 -0
- package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
- package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
- package/dashboard/dist/assets/BzRbDCAD.css +1 -0
- package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
- package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
- package/dashboard/dist/assets/{DNxKz-GV.js → C8uM3AX8.js} +1 -1
- package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
- package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
- package/dashboard/dist/assets/C9jy61eu.js +212 -0
- package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
- package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
- package/dashboard/dist/assets/{CE38zU4U.js → CC63EwFD.js} +1 -1
- package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
- package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
- package/dashboard/dist/assets/{nByHNHoW.js → CZaT3ob_.js} +1 -1
- package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
- package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
- package/dashboard/dist/assets/{tS9mbYZi.js → CgaottFX.js} +1 -1
- package/dashboard/dist/assets/CgaottFX.js.br +0 -0
- package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
- package/dashboard/dist/assets/CzCxAZlW.js +1 -0
- package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
- package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
- package/dashboard/dist/assets/D3iMTYEj.js +1 -0
- package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
- package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
- package/dashboard/dist/assets/{DjcdE6jC.js → D8JNX8kq.js} +1 -1
- package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
- package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
- package/dashboard/dist/assets/DnA8dpj6.js +1 -0
- package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
- package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
- package/dashboard/dist/assets/{DbNoijHm.js → IUexzymk.js} +1 -1
- package/dashboard/dist/assets/IUexzymk.js.br +0 -0
- package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
- package/dashboard/dist/assets/{CZZTvkQZ.js → rttbDbEx.js} +1 -1
- package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
- package/dashboard/dist/assets/rttbDbEx.js.gz +0 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/contracts/practice-exercise-schema.d.ts +216 -0
- package/dist/contracts/practice-exercise-schema.js +314 -0
- package/dist/contracts/shared-types.d.ts +2 -2
- package/dist/contracts/shared-types.js +22 -0
- package/dist/contracts/types.d.ts +20 -0
- package/dist/fs-utils.js +1 -13
- package/dist/http/helpers/auto-continue-engine.js +638 -24
- package/dist/http/helpers/autopilot-runtime.js +22 -7
- package/dist/http/helpers/autopilot-slice-utils.js +0 -2
- package/dist/http/helpers/kickoff-context.js +30 -0
- package/dist/http/helpers/slice-run-projections.js +19 -2
- package/dist/http/index.js +151 -93
- package/dist/http/routes/agent-suite.js +87 -0
- package/dist/http/routes/entities.js +1 -63
- package/dist/http/routes/live-snapshot.d.ts +1 -0
- package/dist/http/routes/live-snapshot.js +15 -4
- package/dist/http/routes/live-terminal.js +1 -108
- package/dist/http/routes/live-triage.js +1 -57
- package/dist/http/routes/mission-control-actions.js +0 -88
- package/dist/http/routes/mission-control-read.js +73 -8
- package/dist/mcp-http-handler.d.ts +3 -0
- package/dist/mcp-http-handler.js +2 -2
- package/dist/openclaw.plugin.json +1 -1
- package/dist/paths.js +14 -2
- package/dist/reporting/rollups.d.ts +0 -12
- package/dist/reporting/rollups.js +0 -35
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -3
- package/dashboard/dist/assets/BXWDRGm-.js +0 -1
- package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js +0 -1
- package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js +0 -8
- package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js +0 -213
- package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
- package/dashboard/dist/assets/DW_rKUic.js +0 -11
- package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
- package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js +0 -1
- package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
- package/dashboard/dist/assets/h5biQs2I.css +0 -1
- package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
- package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
|
@@ -34,11 +34,548 @@ export function createAutoContinueEngine(deps) {
|
|
|
34
34
|
const decisionAutoResolveGuardedEnabled = String(process.env.DECISION_AUTO_RESOLVE_GUARDED_ENABLED ?? "true")
|
|
35
35
|
.trim()
|
|
36
36
|
.toLowerCase() !== "false";
|
|
37
|
+
const questionAutoAnswerPolicyByScope = new Map();
|
|
38
|
+
const pendingQuestionAutoAnswerByScope = new Map();
|
|
39
|
+
const QUESTION_AUTO_ANSWER_DEFAULT_TIMEOUT_SECONDS = readBudgetEnvNumber("ORGX_QUESTION_AUTO_ANSWER_TIMEOUT_SEC", readBudgetEnvNumber("ORGX_QUESTION_AUTO_ANSWER_DELAY_SECONDS", 60, {
|
|
40
|
+
min: 1,
|
|
41
|
+
max: 900,
|
|
42
|
+
}), { min: 1, max: 3600 });
|
|
43
|
+
const QUESTION_AUTO_ANSWER_DEFAULT_ENABLED = String(process.env.ORGX_QUESTION_AUTO_ANSWER_ENABLED ?? "true")
|
|
44
|
+
.trim()
|
|
45
|
+
.toLowerCase() !== "false";
|
|
46
|
+
const QUESTION_AUTO_ANSWER_DEFAULT_MODE = String(process.env.ORGX_QUESTION_AUTO_ANSWER_POLICY ?? "contextual")
|
|
47
|
+
.trim()
|
|
48
|
+
.toLowerCase() === "approve_non_blocking"
|
|
49
|
+
? "approve_non_blocking"
|
|
50
|
+
: String(process.env.ORGX_QUESTION_AUTO_ANSWER_POLICY ?? "contextual")
|
|
51
|
+
.trim()
|
|
52
|
+
.toLowerCase() === "defer_non_blocking"
|
|
53
|
+
? "defer_non_blocking"
|
|
54
|
+
: "contextual";
|
|
55
|
+
const QUESTION_BLOCKING_BEHAVIOR_DEFAULT = String(process.env.ORGX_QUESTION_BLOCKING_BEHAVIOR ?? "require_human")
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase() === "guarded_auto_resolve_then_human"
|
|
58
|
+
? "guarded_auto_resolve_then_human"
|
|
59
|
+
: "require_human";
|
|
60
|
+
const QUESTION_AUTO_ANSWER_DEFAULT_ACTION = String(process.env.ORGX_QUESTION_AUTO_ANSWER_ACTION ?? "approve")
|
|
61
|
+
.trim()
|
|
62
|
+
.toLowerCase() === "reject"
|
|
63
|
+
? "reject"
|
|
64
|
+
: "approve";
|
|
65
|
+
const autoContinueSliceRuns = new Map();
|
|
37
66
|
/** Spread into any metadata object to flag mock-worker activity. */
|
|
38
67
|
function mockMeta(slice) {
|
|
39
68
|
return slice.isMockWorker ? { mock: true } : {};
|
|
40
69
|
}
|
|
70
|
+
function normalizeRuntimeSourceClient(value) {
|
|
71
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
72
|
+
if (!normalized)
|
|
73
|
+
return "unknown";
|
|
74
|
+
if (normalized === "codex")
|
|
75
|
+
return "codex";
|
|
76
|
+
if (normalized === "claude-code" || normalized === "claude_code")
|
|
77
|
+
return "claude-code";
|
|
78
|
+
if (normalized === "openclaw")
|
|
79
|
+
return "openclaw";
|
|
80
|
+
if (normalized === "api")
|
|
81
|
+
return "api";
|
|
82
|
+
return "unknown";
|
|
83
|
+
}
|
|
84
|
+
const normalizeQuestionAutoAnswerPolicy = (runtimeSettings) => {
|
|
85
|
+
const workspaceDefaults = runtimeSettings?.workspace_question_defaults &&
|
|
86
|
+
typeof runtimeSettings.workspace_question_defaults === "object"
|
|
87
|
+
? runtimeSettings.workspace_question_defaults
|
|
88
|
+
: null;
|
|
89
|
+
const enabledRaw = runtimeSettings?.question_auto_answer_enabled;
|
|
90
|
+
const timeoutRaw = runtimeSettings?.question_auto_answer_timeout_sec ??
|
|
91
|
+
runtimeSettings?.question_auto_answer_delay_seconds;
|
|
92
|
+
const workspaceTimeoutRaw = workspaceDefaults?.question_auto_answer_timeout_sec ??
|
|
93
|
+
workspaceDefaults
|
|
94
|
+
?.question_auto_answer_delay_seconds;
|
|
95
|
+
const actionRaw = runtimeSettings?.question_auto_answer_action;
|
|
96
|
+
const modeRaw = runtimeSettings?.question_auto_answer_policy;
|
|
97
|
+
const workspaceModeRaw = workspaceDefaults?.question_auto_answer_policy;
|
|
98
|
+
const blockingBehaviorRaw = runtimeSettings?.question_blocking_behavior;
|
|
99
|
+
const workspaceBlockingBehaviorRaw = workspaceDefaults?.question_blocking_behavior;
|
|
100
|
+
const policyVersionRaw = runtimeSettings?.question_policy_version;
|
|
101
|
+
const timeoutSeconds = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw)
|
|
102
|
+
? Math.max(1, Math.min(3600, Math.floor(timeoutRaw)))
|
|
103
|
+
: typeof workspaceTimeoutRaw === "number" && Number.isFinite(workspaceTimeoutRaw)
|
|
104
|
+
? Math.max(1, Math.min(3600, Math.floor(workspaceTimeoutRaw)))
|
|
105
|
+
: QUESTION_AUTO_ANSWER_DEFAULT_TIMEOUT_SECONDS;
|
|
106
|
+
const mode = modeRaw === "approve_non_blocking" ||
|
|
107
|
+
modeRaw === "defer_non_blocking" ||
|
|
108
|
+
modeRaw === "contextual"
|
|
109
|
+
? modeRaw
|
|
110
|
+
: workspaceModeRaw === "approve_non_blocking" ||
|
|
111
|
+
workspaceModeRaw === "defer_non_blocking" ||
|
|
112
|
+
workspaceModeRaw === "contextual"
|
|
113
|
+
? workspaceModeRaw
|
|
114
|
+
: QUESTION_AUTO_ANSWER_DEFAULT_MODE;
|
|
115
|
+
const action = actionRaw === "reject" || actionRaw === "approve"
|
|
116
|
+
? actionRaw
|
|
117
|
+
: mode === "defer_non_blocking"
|
|
118
|
+
? "reject"
|
|
119
|
+
: QUESTION_AUTO_ANSWER_DEFAULT_ACTION;
|
|
120
|
+
const blockingBehavior = blockingBehaviorRaw === "guarded_auto_resolve_then_human" ||
|
|
121
|
+
blockingBehaviorRaw === "require_human"
|
|
122
|
+
? blockingBehaviorRaw
|
|
123
|
+
: workspaceBlockingBehaviorRaw === "guarded_auto_resolve_then_human" ||
|
|
124
|
+
workspaceBlockingBehaviorRaw === "require_human"
|
|
125
|
+
? workspaceBlockingBehaviorRaw
|
|
126
|
+
: QUESTION_BLOCKING_BEHAVIOR_DEFAULT;
|
|
127
|
+
const enabled = typeof enabledRaw === "boolean"
|
|
128
|
+
? enabledRaw
|
|
129
|
+
: typeof workspaceDefaults?.question_auto_answer_enabled === "boolean"
|
|
130
|
+
? workspaceDefaults.question_auto_answer_enabled
|
|
131
|
+
: QUESTION_AUTO_ANSWER_DEFAULT_ENABLED;
|
|
132
|
+
const policyVersion = typeof policyVersionRaw === "number" && Number.isFinite(policyVersionRaw)
|
|
133
|
+
? Math.max(1, Math.min(10, Math.floor(policyVersionRaw)))
|
|
134
|
+
: 1;
|
|
135
|
+
return {
|
|
136
|
+
enabled,
|
|
137
|
+
timeoutSeconds,
|
|
138
|
+
mode,
|
|
139
|
+
action,
|
|
140
|
+
blockingBehavior,
|
|
141
|
+
policyVersion,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const questionScopeKey = (initiativeId, workstreamId) => {
|
|
145
|
+
const normalizedInitiativeId = (initiativeId ?? "").trim() || "unknown_initiative";
|
|
146
|
+
const normalizedWorkstreamId = (workstreamId ?? "").trim() || "all_workstreams";
|
|
147
|
+
return `${normalizedInitiativeId}::${normalizedWorkstreamId}`;
|
|
148
|
+
};
|
|
149
|
+
const resolveQuestionPolicy = (initiativeId, workstreamId) => {
|
|
150
|
+
const scoped = questionAutoAnswerPolicyByScope.get(questionScopeKey(initiativeId, workstreamId));
|
|
151
|
+
if (scoped)
|
|
152
|
+
return scoped;
|
|
153
|
+
const initiativeWide = questionAutoAnswerPolicyByScope.get(questionScopeKey(initiativeId, null));
|
|
154
|
+
if (initiativeWide)
|
|
155
|
+
return initiativeWide;
|
|
156
|
+
return {
|
|
157
|
+
enabled: QUESTION_AUTO_ANSWER_DEFAULT_ENABLED,
|
|
158
|
+
timeoutSeconds: QUESTION_AUTO_ANSWER_DEFAULT_TIMEOUT_SECONDS,
|
|
159
|
+
mode: QUESTION_AUTO_ANSWER_DEFAULT_MODE,
|
|
160
|
+
action: QUESTION_AUTO_ANSWER_DEFAULT_ACTION,
|
|
161
|
+
blockingBehavior: QUESTION_BLOCKING_BEHAVIOR_DEFAULT,
|
|
162
|
+
policyVersion: 1,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
const clearQuestionAutoAnswerStateForInitiative = (initiativeId) => {
|
|
166
|
+
const normalizedInitiativeId = (initiativeId ?? "").trim();
|
|
167
|
+
if (!normalizedInitiativeId)
|
|
168
|
+
return;
|
|
169
|
+
for (const [key, pending] of pendingQuestionAutoAnswerByScope.entries()) {
|
|
170
|
+
if ((pending.initiativeId ?? "").trim() !== normalizedInitiativeId)
|
|
171
|
+
continue;
|
|
172
|
+
if (pending.timer) {
|
|
173
|
+
clearTimeout(pending.timer);
|
|
174
|
+
}
|
|
175
|
+
pendingQuestionAutoAnswerByScope.delete(key);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const processQuestionAutoAnswer = async (key, pending) => {
|
|
179
|
+
pendingQuestionAutoAnswerByScope.delete(key);
|
|
180
|
+
const note = pending.action === "approve"
|
|
181
|
+
? "Auto-approved after timeout: no human answer received within configured delay."
|
|
182
|
+
: "Auto-rejected after timeout: no human answer received within configured delay.";
|
|
183
|
+
const decisionIds = pending.decisionIds;
|
|
184
|
+
if (decisionIds.length === 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await emitActivitySafe({
|
|
188
|
+
initiativeId: pending.initiativeId,
|
|
189
|
+
runId: pending.sourceRunId,
|
|
190
|
+
correlationId: pending.sourceRunId,
|
|
191
|
+
phase: "review",
|
|
192
|
+
level: "info",
|
|
193
|
+
progressPct: 0,
|
|
194
|
+
nextStep: "Applying question answer policy to unresolved items.",
|
|
195
|
+
message: "Question auto-answered after timeout; applying decision updates.",
|
|
196
|
+
metadata: {
|
|
197
|
+
event: "question_auto_answered",
|
|
198
|
+
action_type: normalizeActivityActionType("question_auto_answered"),
|
|
199
|
+
action_phase: normalizeActivityActionPhase("review"),
|
|
200
|
+
initiative_id: pending.initiativeId,
|
|
201
|
+
workstream_id: pending.workstreamId,
|
|
202
|
+
source_run_id: pending.sourceRunId,
|
|
203
|
+
source_client: pending.sourceClient,
|
|
204
|
+
decision_ids: decisionIds,
|
|
205
|
+
decision_count: decisionIds.length,
|
|
206
|
+
decision_action: pending.action,
|
|
207
|
+
timeout_seconds_applied: pending.timeoutSeconds,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
let applied = 0;
|
|
211
|
+
let failed = 0;
|
|
212
|
+
const failures = [];
|
|
213
|
+
for (const decisionId of decisionIds) {
|
|
214
|
+
try {
|
|
215
|
+
await client.decideDecision(decisionId, pending.action, { note });
|
|
216
|
+
applied += 1;
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
failed += 1;
|
|
220
|
+
failures.push({
|
|
221
|
+
id: decisionId,
|
|
222
|
+
error: safeErrorMessage(err),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
await emitActivitySafe({
|
|
227
|
+
initiativeId: pending.initiativeId,
|
|
228
|
+
runId: pending.sourceRunId,
|
|
229
|
+
correlationId: pending.sourceRunId,
|
|
230
|
+
phase: failed > 0 ? "blocked" : "review",
|
|
231
|
+
level: failed > 0 ? "warn" : "info",
|
|
232
|
+
progressPct: 100,
|
|
233
|
+
nextStep: failed > 0
|
|
234
|
+
? "Review failed auto-answer decisions and resolve manually."
|
|
235
|
+
: "Decision queue was auto-resolved; run can continue.",
|
|
236
|
+
message: failed > 0
|
|
237
|
+
? `Question answers processed (${applied} applied, ${failed} failed).`
|
|
238
|
+
: `Question answer ${pending.action} applied to ${applied} queued items.`,
|
|
239
|
+
metadata: {
|
|
240
|
+
event: failed > 0 ? "question_answer_failed" : "question_answer_applied",
|
|
241
|
+
action_type: normalizeActivityActionType(failed > 0 ? "question_answer_failed" : "question_answer_applied"),
|
|
242
|
+
action_phase: normalizeActivityActionPhase(failed > 0 ? "blocked" : "review"),
|
|
243
|
+
initiative_id: pending.initiativeId,
|
|
244
|
+
workstream_id: pending.workstreamId,
|
|
245
|
+
source_run_id: pending.sourceRunId,
|
|
246
|
+
source_client: pending.sourceClient,
|
|
247
|
+
question_policy_mode: pending.mode,
|
|
248
|
+
question_policy_version: pending.policyVersion,
|
|
249
|
+
decision_action: pending.action,
|
|
250
|
+
decision_ids: decisionIds,
|
|
251
|
+
decision_count: decisionIds.length,
|
|
252
|
+
applied_count: applied,
|
|
253
|
+
failed_count: failed,
|
|
254
|
+
resolution_source: "policy_timeout",
|
|
255
|
+
timeout_seconds_applied: pending.timeoutSeconds,
|
|
256
|
+
failures,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
const armQuestionAutoAnswerTimer = (key, pending, delaySeconds) => {
|
|
261
|
+
if (pending.timer) {
|
|
262
|
+
clearTimeout(pending.timer);
|
|
263
|
+
}
|
|
264
|
+
pending.timer = setTimeout(() => {
|
|
265
|
+
void (async () => {
|
|
266
|
+
try {
|
|
267
|
+
await emitActivitySafe({
|
|
268
|
+
initiativeId: pending.initiativeId,
|
|
269
|
+
runId: pending.sourceRunId,
|
|
270
|
+
correlationId: pending.sourceRunId,
|
|
271
|
+
phase: "review",
|
|
272
|
+
level: "info",
|
|
273
|
+
progressPct: 100,
|
|
274
|
+
nextStep: "Applying configured decision action sequentially.",
|
|
275
|
+
message: "Question timeout reached; applying auto-answer policy.",
|
|
276
|
+
metadata: {
|
|
277
|
+
event: "question_timeout_started",
|
|
278
|
+
action_type: normalizeActivityActionType("question_timeout_started"),
|
|
279
|
+
action_phase: normalizeActivityActionPhase("review"),
|
|
280
|
+
initiative_id: pending.initiativeId,
|
|
281
|
+
workstream_id: pending.workstreamId,
|
|
282
|
+
source_run_id: pending.sourceRunId,
|
|
283
|
+
source_client: pending.sourceClient,
|
|
284
|
+
decision_ids: pending.decisionIds,
|
|
285
|
+
decision_count: pending.decisionIds.length,
|
|
286
|
+
decision_action: pending.action,
|
|
287
|
+
question_policy_mode: pending.mode,
|
|
288
|
+
question_policy_version: pending.policyVersion,
|
|
289
|
+
timeout_seconds_applied: pending.timeoutSeconds,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
await processQuestionAutoAnswer(key, pending);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
await emitActivitySafe({
|
|
296
|
+
initiativeId: pending.initiativeId,
|
|
297
|
+
runId: pending.sourceRunId,
|
|
298
|
+
correlationId: pending.sourceRunId,
|
|
299
|
+
phase: "blocked",
|
|
300
|
+
level: "warn",
|
|
301
|
+
progressPct: 100,
|
|
302
|
+
nextStep: "Review and resolve the queued question manually.",
|
|
303
|
+
message: "Question auto-answer failed before apply.",
|
|
304
|
+
metadata: {
|
|
305
|
+
event: "question_answer_failed",
|
|
306
|
+
action_type: normalizeActivityActionType("question_answer_failed"),
|
|
307
|
+
action_phase: normalizeActivityActionPhase("blocked"),
|
|
308
|
+
initiative_id: pending.initiativeId,
|
|
309
|
+
workstream_id: pending.workstreamId,
|
|
310
|
+
source_run_id: pending.sourceRunId,
|
|
311
|
+
source_client: pending.sourceClient,
|
|
312
|
+
decision_ids: pending.decisionIds,
|
|
313
|
+
decision_count: pending.decisionIds.length,
|
|
314
|
+
decision_action: pending.action,
|
|
315
|
+
failed_count: pending.decisionIds.length,
|
|
316
|
+
resolution_source: "policy_timeout",
|
|
317
|
+
timeout_seconds_applied: pending.timeoutSeconds,
|
|
318
|
+
question_policy_mode: pending.mode,
|
|
319
|
+
question_policy_version: pending.policyVersion,
|
|
320
|
+
error: safeErrorMessage(err),
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
})();
|
|
325
|
+
}, delaySeconds * 1_000);
|
|
326
|
+
pending.timer.unref?.();
|
|
327
|
+
};
|
|
328
|
+
const scheduleQuestionAutoAnswer = async (input) => {
|
|
329
|
+
const decisionIds = dedupeStrings(input.decisionIds
|
|
330
|
+
.map((entry) => (entry ?? "").trim())
|
|
331
|
+
.filter(Boolean));
|
|
332
|
+
if (decisionIds.length === 0)
|
|
333
|
+
return;
|
|
334
|
+
const policy = resolveQuestionPolicy(input.initiativeId, input.workstreamId);
|
|
335
|
+
await emitActivitySafe({
|
|
336
|
+
initiativeId: input.initiativeId,
|
|
337
|
+
runId: input.sourceRunId,
|
|
338
|
+
correlationId: input.sourceRunId,
|
|
339
|
+
phase: "review",
|
|
340
|
+
level: "info",
|
|
341
|
+
progressPct: 0,
|
|
342
|
+
nextStep: input.blocking
|
|
343
|
+
? "Blocking question requires human review."
|
|
344
|
+
: `Auto-answer in ${policy.timeoutSeconds}s unless human responds.`,
|
|
345
|
+
message: input.blocking
|
|
346
|
+
? "Blocking question surfaced for human decision."
|
|
347
|
+
: "Question surfaced and queued for timeout policy.",
|
|
348
|
+
metadata: {
|
|
349
|
+
event: "question_asked",
|
|
350
|
+
action_type: normalizeActivityActionType("question_asked"),
|
|
351
|
+
action_phase: normalizeActivityActionPhase("review"),
|
|
352
|
+
initiative_id: input.initiativeId,
|
|
353
|
+
workstream_id: input.workstreamId,
|
|
354
|
+
source_run_id: input.sourceRunId,
|
|
355
|
+
source_client: input.sourceClient,
|
|
356
|
+
decision_ids: decisionIds,
|
|
357
|
+
decision_count: decisionIds.length,
|
|
358
|
+
blocking: input.blocking,
|
|
359
|
+
question_policy_mode: policy.mode,
|
|
360
|
+
question_policy_version: policy.policyVersion,
|
|
361
|
+
timeout_seconds_applied: policy.timeoutSeconds,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
if (input.blocking) {
|
|
365
|
+
await emitActivitySafe({
|
|
366
|
+
initiativeId: input.initiativeId,
|
|
367
|
+
runId: input.sourceRunId,
|
|
368
|
+
correlationId: input.sourceRunId,
|
|
369
|
+
phase: "blocked",
|
|
370
|
+
level: "info",
|
|
371
|
+
progressPct: 0,
|
|
372
|
+
nextStep: policy.blockingBehavior === "guarded_auto_resolve_then_human" &&
|
|
373
|
+
decisionAutoResolveGuardedEnabled
|
|
374
|
+
? "Awaiting guarded remediation and/or human decision."
|
|
375
|
+
: "Awaiting human decision response.",
|
|
376
|
+
message: policy.blockingBehavior === "guarded_auto_resolve_then_human" &&
|
|
377
|
+
decisionAutoResolveGuardedEnabled
|
|
378
|
+
? "Blocking question requires human decision after guarded remediation."
|
|
379
|
+
: "Blocking question requires human decision.",
|
|
380
|
+
metadata: {
|
|
381
|
+
event: "review_item_created",
|
|
382
|
+
action_type: normalizeActivityActionType("review_item_created"),
|
|
383
|
+
action_phase: normalizeActivityActionPhase("blocked"),
|
|
384
|
+
initiative_id: input.initiativeId,
|
|
385
|
+
workstream_id: input.workstreamId,
|
|
386
|
+
source_run_id: input.sourceRunId,
|
|
387
|
+
source_client: input.sourceClient,
|
|
388
|
+
decision_ids: decisionIds,
|
|
389
|
+
decision_count: decisionIds.length,
|
|
390
|
+
blocking: true,
|
|
391
|
+
reason: "blocking_question_requires_human",
|
|
392
|
+
question_policy_mode: policy.mode,
|
|
393
|
+
question_policy_version: policy.policyVersion,
|
|
394
|
+
question_blocking_behavior: policy.blockingBehavior,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (!policy.enabled) {
|
|
400
|
+
await emitActivitySafe({
|
|
401
|
+
initiativeId: input.initiativeId,
|
|
402
|
+
runId: input.sourceRunId,
|
|
403
|
+
correlationId: input.sourceRunId,
|
|
404
|
+
phase: "review",
|
|
405
|
+
level: "info",
|
|
406
|
+
progressPct: 0,
|
|
407
|
+
nextStep: "Awaiting human decision response.",
|
|
408
|
+
message: "Question auto-answer is disabled for this agent policy.",
|
|
409
|
+
metadata: {
|
|
410
|
+
event: "review_item_created",
|
|
411
|
+
action_type: normalizeActivityActionType("review_item_created"),
|
|
412
|
+
action_phase: normalizeActivityActionPhase("review"),
|
|
413
|
+
initiative_id: input.initiativeId,
|
|
414
|
+
workstream_id: input.workstreamId,
|
|
415
|
+
source_run_id: input.sourceRunId,
|
|
416
|
+
source_client: input.sourceClient,
|
|
417
|
+
decision_ids: decisionIds,
|
|
418
|
+
reason: "policy_disabled",
|
|
419
|
+
question_policy_mode: policy.mode,
|
|
420
|
+
question_policy_version: policy.policyVersion,
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const key = questionScopeKey(input.initiativeId, input.workstreamId);
|
|
426
|
+
const dueAtEpoch = Date.now() + policy.timeoutSeconds * 1_000;
|
|
427
|
+
const existing = pendingQuestionAutoAnswerByScope.get(key);
|
|
428
|
+
if (existing) {
|
|
429
|
+
existing.decisionIds = dedupeStrings([...existing.decisionIds, ...decisionIds]);
|
|
430
|
+
existing.sourceRunId = input.sourceRunId ?? existing.sourceRunId;
|
|
431
|
+
existing.sourceClient = input.sourceClient || existing.sourceClient;
|
|
432
|
+
existing.action = policy.action;
|
|
433
|
+
existing.mode = policy.mode;
|
|
434
|
+
existing.policyVersion = policy.policyVersion;
|
|
435
|
+
existing.timeoutSeconds = policy.timeoutSeconds;
|
|
436
|
+
existing.dueAt = new Date(dueAtEpoch).toISOString();
|
|
437
|
+
armQuestionAutoAnswerTimer(key, existing, policy.timeoutSeconds);
|
|
438
|
+
await emitActivitySafe({
|
|
439
|
+
initiativeId: input.initiativeId,
|
|
440
|
+
runId: input.sourceRunId,
|
|
441
|
+
correlationId: input.sourceRunId,
|
|
442
|
+
phase: "review",
|
|
443
|
+
level: "info",
|
|
444
|
+
progressPct: 0,
|
|
445
|
+
nextStep: `Auto-answer in ${policy.timeoutSeconds}s unless human responds.`,
|
|
446
|
+
message: "Extended timeout for queued unanswered decision(s).",
|
|
447
|
+
metadata: {
|
|
448
|
+
event: "question_timeout_started",
|
|
449
|
+
action_type: normalizeActivityActionType("question_timeout_started"),
|
|
450
|
+
action_phase: normalizeActivityActionPhase("review"),
|
|
451
|
+
initiative_id: input.initiativeId,
|
|
452
|
+
workstream_id: input.workstreamId,
|
|
453
|
+
source_run_id: input.sourceRunId,
|
|
454
|
+
source_client: input.sourceClient,
|
|
455
|
+
decision_ids: existing.decisionIds,
|
|
456
|
+
decision_count: existing.decisionIds.length,
|
|
457
|
+
decision_action: existing.action,
|
|
458
|
+
timeout_seconds_applied: policy.timeoutSeconds,
|
|
459
|
+
question_policy_mode: policy.mode,
|
|
460
|
+
question_policy_version: policy.policyVersion,
|
|
461
|
+
due_at: existing.dueAt,
|
|
462
|
+
reason: input.reason,
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const pending = {
|
|
468
|
+
key,
|
|
469
|
+
initiativeId: input.initiativeId ?? "",
|
|
470
|
+
workstreamId: input.workstreamId ?? null,
|
|
471
|
+
sourceRunId: input.sourceRunId ?? null,
|
|
472
|
+
sourceClient: input.sourceClient,
|
|
473
|
+
action: policy.action,
|
|
474
|
+
mode: policy.mode,
|
|
475
|
+
policyVersion: policy.policyVersion,
|
|
476
|
+
timeoutSeconds: policy.timeoutSeconds,
|
|
477
|
+
dueAt: new Date(dueAtEpoch).toISOString(),
|
|
478
|
+
timer: null,
|
|
479
|
+
decisionIds,
|
|
480
|
+
};
|
|
481
|
+
armQuestionAutoAnswerTimer(key, pending, policy.timeoutSeconds);
|
|
482
|
+
pendingQuestionAutoAnswerByScope.set(key, pending);
|
|
483
|
+
await emitActivitySafe({
|
|
484
|
+
initiativeId: input.initiativeId,
|
|
485
|
+
runId: input.sourceRunId,
|
|
486
|
+
correlationId: input.sourceRunId,
|
|
487
|
+
phase: "review",
|
|
488
|
+
level: "info",
|
|
489
|
+
progressPct: 0,
|
|
490
|
+
nextStep: `Auto-answer in ${policy.timeoutSeconds}s unless human responds.`,
|
|
491
|
+
message: "Queued unanswered decision(s) for timeout auto-answer.",
|
|
492
|
+
metadata: {
|
|
493
|
+
event: "question_timeout_started",
|
|
494
|
+
action_type: normalizeActivityActionType("question_timeout_started"),
|
|
495
|
+
action_phase: normalizeActivityActionPhase("review"),
|
|
496
|
+
initiative_id: input.initiativeId,
|
|
497
|
+
workstream_id: input.workstreamId,
|
|
498
|
+
source_run_id: input.sourceRunId,
|
|
499
|
+
source_client: input.sourceClient,
|
|
500
|
+
decision_ids: decisionIds,
|
|
501
|
+
decision_count: decisionIds.length,
|
|
502
|
+
decision_action: policy.action,
|
|
503
|
+
timeout_seconds_applied: policy.timeoutSeconds,
|
|
504
|
+
question_policy_mode: policy.mode,
|
|
505
|
+
question_policy_version: policy.policyVersion,
|
|
506
|
+
due_at: pending.dueAt,
|
|
507
|
+
reason: input.reason,
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
};
|
|
41
511
|
const requestDecisionQueued = async (input) => {
|
|
512
|
+
const asRecord = (value) => {
|
|
513
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
514
|
+
return null;
|
|
515
|
+
return value;
|
|
516
|
+
};
|
|
517
|
+
const normalizeId = (value) => {
|
|
518
|
+
if (typeof value !== "string")
|
|
519
|
+
return null;
|
|
520
|
+
const trimmed = value.trim();
|
|
521
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
522
|
+
};
|
|
523
|
+
const normalizeLower = (value) => {
|
|
524
|
+
if (typeof value !== "string")
|
|
525
|
+
return "";
|
|
526
|
+
return value.trim().toLowerCase();
|
|
527
|
+
};
|
|
528
|
+
const recoverQueuedDecisionIds = async (recoverInput) => {
|
|
529
|
+
try {
|
|
530
|
+
const pending = await client.getLiveDecisions({ status: "pending", limit: 100 });
|
|
531
|
+
const rows = Array.isArray(pending?.decisions) ? pending.decisions : [];
|
|
532
|
+
const wantedTitle = normalizeLower(recoverInput.title);
|
|
533
|
+
const wantedRunId = normalizeLower(recoverInput.sourceRunId ?? "");
|
|
534
|
+
const wantedWorkstreamId = normalizeLower(recoverInput.workstreamId ?? "");
|
|
535
|
+
const recentThreshold = Date.now() - 10 * 60 * 1_000;
|
|
536
|
+
const ids = [];
|
|
537
|
+
const seen = new Set();
|
|
538
|
+
for (const row of rows) {
|
|
539
|
+
const record = asRecord(row);
|
|
540
|
+
if (!record)
|
|
541
|
+
continue;
|
|
542
|
+
const id = normalizeId(record.id) ??
|
|
543
|
+
normalizeId(record.entity_id) ??
|
|
544
|
+
normalizeId(record.decision_id);
|
|
545
|
+
if (!id || seen.has(id))
|
|
546
|
+
continue;
|
|
547
|
+
const metadata = asRecord(record.metadata);
|
|
548
|
+
const sourceRef = asRecord(record.source_ref) ?? asRecord(metadata?.source_ref);
|
|
549
|
+
const rowWorkstreamId = normalizeLower(record.workstream_id) ||
|
|
550
|
+
normalizeLower(record.workstreamId) ||
|
|
551
|
+
normalizeLower(metadata?.source_stream_id) ||
|
|
552
|
+
normalizeLower(sourceRef?.workstream_id) ||
|
|
553
|
+
normalizeLower(sourceRef?.stream_id);
|
|
554
|
+
const rowRunId = normalizeLower(record.source_run_id) ||
|
|
555
|
+
normalizeLower(record.sourceRunId) ||
|
|
556
|
+
normalizeLower(metadata?.run_id) ||
|
|
557
|
+
normalizeLower(metadata?.correlation_id) ||
|
|
558
|
+
normalizeLower(sourceRef?.run_id);
|
|
559
|
+
const rowTitle = normalizeLower(record.title) || normalizeLower(metadata?.title);
|
|
560
|
+
const updatedAtRaw = normalizeId(record.updated_at) ?? normalizeId(record.created_at);
|
|
561
|
+
const updatedAtEpoch = updatedAtRaw ? Date.parse(updatedAtRaw) : NaN;
|
|
562
|
+
const recentEnough = !Number.isFinite(updatedAtEpoch) || updatedAtEpoch >= recentThreshold;
|
|
563
|
+
const workstreamMatches = !wantedWorkstreamId || rowWorkstreamId === wantedWorkstreamId;
|
|
564
|
+
const runMatches = Boolean(wantedRunId) && rowRunId === wantedRunId;
|
|
565
|
+
const titleMatches = Boolean(wantedTitle) && rowTitle === wantedTitle;
|
|
566
|
+
if (!workstreamMatches)
|
|
567
|
+
continue;
|
|
568
|
+
if (!(runMatches || (titleMatches && recentEnough)))
|
|
569
|
+
continue;
|
|
570
|
+
seen.add(id);
|
|
571
|
+
ids.push(id);
|
|
572
|
+
}
|
|
573
|
+
return ids;
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
return [];
|
|
577
|
+
}
|
|
578
|
+
};
|
|
42
579
|
const inferredRunId = (typeof input.sourceRunId === "string" && input.sourceRunId.trim().length > 0
|
|
43
580
|
? input.sourceRunId.trim()
|
|
44
581
|
: null) ??
|
|
@@ -57,6 +594,18 @@ export function createAutoContinueEngine(deps) {
|
|
|
57
594
|
const sourceRefBase = input.sourceRef && typeof input.sourceRef === "object" && !Array.isArray(input.sourceRef)
|
|
58
595
|
? input.sourceRef
|
|
59
596
|
: {};
|
|
597
|
+
const metadataBase = input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata)
|
|
598
|
+
? input.metadata
|
|
599
|
+
: {};
|
|
600
|
+
const metadataSourceClient = (typeof metadataBase.source_client === "string" && metadataBase.source_client.trim().length > 0
|
|
601
|
+
? metadataBase.source_client.trim()
|
|
602
|
+
: null) ??
|
|
603
|
+
(typeof metadataBase.sourceClient === "string" && metadataBase.sourceClient.trim().length > 0
|
|
604
|
+
? metadataBase.sourceClient.trim()
|
|
605
|
+
: null);
|
|
606
|
+
const inferredSourceClient = normalizeRuntimeSourceClient(metadataSourceClient ??
|
|
607
|
+
process.env.ORGX_AUTOPILOT_EXECUTOR ??
|
|
608
|
+
process.env.ORGX_AUTOPILOT_WORKER_KIND);
|
|
60
609
|
const normalizedInput = {
|
|
61
610
|
...input,
|
|
62
611
|
sourceRunId: inferredRunId,
|
|
@@ -68,25 +617,65 @@ export function createAutoContinueEngine(deps) {
|
|
|
68
617
|
session_id: sourceRefBase.session_id ?? inferredSessionId,
|
|
69
618
|
stream_id: sourceRefBase.stream_id ?? inferredStreamId,
|
|
70
619
|
workstream_id: sourceRefBase.workstream_id ?? input.workstreamId ?? null,
|
|
620
|
+
source_client: sourceRefBase.source_client ??
|
|
621
|
+
sourceRefBase.sourceClient ??
|
|
622
|
+
inferredSourceClient,
|
|
71
623
|
},
|
|
72
624
|
metadata: {
|
|
73
|
-
...
|
|
625
|
+
...metadataBase,
|
|
74
626
|
source_system: input.sourceSystem ?? null,
|
|
75
627
|
conflict_source: input.conflictSource ?? null,
|
|
628
|
+
source_client: metadataBase.source_client ??
|
|
629
|
+
metadataBase.sourceClient ??
|
|
630
|
+
inferredSourceClient,
|
|
76
631
|
},
|
|
77
632
|
};
|
|
633
|
+
const linkedSlice = inferredRunId ? autoContinueSliceRuns.get(inferredRunId) ?? null : null;
|
|
634
|
+
const sourceClientFromInput = typeof normalizedInput.metadata?.source_client === "string"
|
|
635
|
+
? normalizedInput.metadata.source_client
|
|
636
|
+
: null;
|
|
637
|
+
const sourceClient = sourceClientFromInput ??
|
|
638
|
+
linkedSlice?.sourceClient ??
|
|
639
|
+
"unknown";
|
|
640
|
+
const scopedWorkstreamId = ((typeof normalizedInput.workstreamId === "string" &&
|
|
641
|
+
normalizedInput.workstreamId.trim().length > 0
|
|
642
|
+
? normalizedInput.workstreamId.trim()
|
|
643
|
+
: null) ??
|
|
644
|
+
inferredStreamId ??
|
|
645
|
+
linkedSlice?.workstreamId ??
|
|
646
|
+
null);
|
|
78
647
|
const result = await requestDecisionSafe(normalizedInput);
|
|
79
648
|
if (typeof result === "boolean") {
|
|
80
649
|
return { queued: result, decisionIds: [] };
|
|
81
650
|
}
|
|
82
651
|
if (result && typeof result === "object" && "queued" in result) {
|
|
83
652
|
const record = result;
|
|
84
|
-
|
|
653
|
+
let decisionIds = Array.isArray(record.decisionIds)
|
|
85
654
|
? record.decisionIds
|
|
86
655
|
.filter((entry) => typeof entry === "string")
|
|
87
656
|
.map((entry) => entry.trim())
|
|
88
657
|
.filter(Boolean)
|
|
89
658
|
: [];
|
|
659
|
+
if (Boolean(record.queued) && decisionIds.length === 0) {
|
|
660
|
+
decisionIds = await recoverQueuedDecisionIds({
|
|
661
|
+
title: normalizedInput.title,
|
|
662
|
+
sourceRunId: inferredRunId,
|
|
663
|
+
workstreamId: scopedWorkstreamId,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (Boolean(record.queued) && decisionIds.length > 0) {
|
|
667
|
+
await scheduleQuestionAutoAnswer({
|
|
668
|
+
initiativeId: normalizedInput.initiativeId,
|
|
669
|
+
workstreamId: scopedWorkstreamId,
|
|
670
|
+
sourceRunId: inferredRunId,
|
|
671
|
+
sourceClient,
|
|
672
|
+
decisionIds,
|
|
673
|
+
blocking: Boolean(normalizedInput.blocking),
|
|
674
|
+
reason: typeof normalizedInput.conflictSource === "string"
|
|
675
|
+
? normalizedInput.conflictSource
|
|
676
|
+
: null,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
90
679
|
return {
|
|
91
680
|
queued: Boolean(record.queued),
|
|
92
681
|
decisionIds,
|
|
@@ -152,6 +741,20 @@ export function createAutoContinueEngine(deps) {
|
|
|
152
741
|
return "status_updates_applied";
|
|
153
742
|
if (eventName.includes("artifact_registered"))
|
|
154
743
|
return "artifact_registered";
|
|
744
|
+
if (eventName.includes("question_asked"))
|
|
745
|
+
return "question_asked";
|
|
746
|
+
if (eventName.includes("question_timeout_started"))
|
|
747
|
+
return "question_timeout_started";
|
|
748
|
+
if (eventName.includes("question_auto_answered"))
|
|
749
|
+
return "question_auto_answered";
|
|
750
|
+
if (eventName.includes("question_answer_applied"))
|
|
751
|
+
return "question_answer_applied";
|
|
752
|
+
if (eventName.includes("question_answer_failed"))
|
|
753
|
+
return "question_answer_failed";
|
|
754
|
+
if (eventName.includes("review_item_created"))
|
|
755
|
+
return "review_item_created";
|
|
756
|
+
if (eventName.includes("review_item_resolved"))
|
|
757
|
+
return "review_item_resolved";
|
|
155
758
|
if (eventName.includes("decision_requested"))
|
|
156
759
|
return "decision_requested";
|
|
157
760
|
if (eventName.includes("decision_resolved"))
|
|
@@ -229,6 +832,8 @@ export function createAutoContinueEngine(deps) {
|
|
|
229
832
|
run_id: input.slice?.runId ?? null,
|
|
230
833
|
slice_run_id: input.slice?.runId ?? null,
|
|
231
834
|
correlation_id: input.slice?.runId ?? null,
|
|
835
|
+
source_client: input.slice?.sourceClient ?? "unknown",
|
|
836
|
+
runtime_client: input.slice?.sourceClient ?? "unknown",
|
|
232
837
|
workstream_id: workstreamId,
|
|
233
838
|
workstream_title: input.workstreamTitle ?? input.slice?.workstreamTitle ?? null,
|
|
234
839
|
task_id: taskId,
|
|
@@ -266,7 +871,6 @@ export function createAutoContinueEngine(deps) {
|
|
|
266
871
|
...(input.extra ?? {}),
|
|
267
872
|
};
|
|
268
873
|
};
|
|
269
|
-
const autoContinueSliceRuns = new Map();
|
|
270
874
|
// Keep child handles alive so stdout/stderr capture remains reliable even when the process is detached.
|
|
271
875
|
const autoContinueSliceChildren = new Map();
|
|
272
876
|
const autoContinueSliceLastHeartbeatMs = new Map();
|
|
@@ -765,6 +1369,14 @@ export function createAutoContinueEngine(deps) {
|
|
|
765
1369
|
await updateInitiativeMetadata(input.initiativeId, patch);
|
|
766
1370
|
}
|
|
767
1371
|
async function stopAutoContinueRun(input) {
|
|
1372
|
+
const decisionRequired = input.reason === "blocked" && input.decisionRequired === true;
|
|
1373
|
+
const decisionIds = Array.isArray(input.decisionIds)
|
|
1374
|
+
? input.decisionIds
|
|
1375
|
+
.filter((entry) => typeof entry === "string")
|
|
1376
|
+
.map((entry) => entry.trim())
|
|
1377
|
+
.filter(Boolean)
|
|
1378
|
+
: [];
|
|
1379
|
+
const preserveQuestionAutoAnswerState = input.reason === "blocked" && decisionRequired && decisionIds.length > 0;
|
|
768
1380
|
const now = new Date().toISOString();
|
|
769
1381
|
ensureRunInternals(input.run);
|
|
770
1382
|
const activeRunIds = listActiveSliceRunIds(input.run);
|
|
@@ -791,6 +1403,9 @@ export function createAutoContinueEngine(deps) {
|
|
|
791
1403
|
if (input.error)
|
|
792
1404
|
input.run.lastError = input.error;
|
|
793
1405
|
clearSpawnGuardRetryStateForInitiative(input.run.initiativeId);
|
|
1406
|
+
if (!preserveQuestionAutoAnswerState) {
|
|
1407
|
+
clearQuestionAutoAnswerStateForInitiative(input.run.initiativeId);
|
|
1408
|
+
}
|
|
794
1409
|
for (const runId of activeRunIds) {
|
|
795
1410
|
clearAutoContinueSliceTransientState(runId);
|
|
796
1411
|
}
|
|
@@ -820,13 +1435,6 @@ export function createAutoContinueEngine(deps) {
|
|
|
820
1435
|
? input.run.allowedWorkstreamIds[0]
|
|
821
1436
|
: null;
|
|
822
1437
|
const scopeSuffix = scopedWorkstreamId ? ` [workstream ${scopedWorkstreamId}]` : "";
|
|
823
|
-
const decisionRequired = input.reason === "blocked" && input.decisionRequired === true;
|
|
824
|
-
const decisionIds = Array.isArray(input.decisionIds)
|
|
825
|
-
? input.decisionIds
|
|
826
|
-
.filter((entry) => typeof entry === "string")
|
|
827
|
-
.map((entry) => entry.trim())
|
|
828
|
-
.filter(Boolean)
|
|
829
|
-
: [];
|
|
830
1438
|
const budgetValue = typeof input.run.tokenBudget === "number" ? input.run.tokenBudget : "unbounded";
|
|
831
1439
|
const message = input.reason === "completed"
|
|
832
1440
|
? `Autopilot stopped: current dispatch scope completed${scopeSuffix}.`
|
|
@@ -1291,13 +1899,11 @@ export function createAutoContinueEngine(deps) {
|
|
|
1291
1899
|
item?.blocking === true);
|
|
1292
1900
|
};
|
|
1293
1901
|
const decisions = allDecisions.filter((item) => !isParserSyntheticFallbackDecision(item));
|
|
1294
|
-
const
|
|
1295
|
-
const
|
|
1296
|
-
const effectiveParsedStatus = parsedStatus === "completed" &&
|
|
1902
|
+
const normalizedBlockingDecisionCount = allDecisions.filter((item) => typeof item.blocking === "boolean" ? item.blocking : defaultDecisionBlocking).length;
|
|
1903
|
+
const normalizedNonBlockingDecisionCount = Math.max(0, allDecisions.length - normalizedBlockingDecisionCount);
|
|
1904
|
+
const effectiveParsedStatus = parsedStatus === "completed" && normalizedBlockingDecisionCount > 0
|
|
1297
1905
|
? "needs_decision"
|
|
1298
|
-
: parsedStatus
|
|
1299
|
-
? "completed"
|
|
1300
|
-
: parsedStatus;
|
|
1906
|
+
: parsedStatus;
|
|
1301
1907
|
slice.status =
|
|
1302
1908
|
effectiveParsedStatus === "completed"
|
|
1303
1909
|
? "completed"
|
|
@@ -1574,9 +2180,9 @@ export function createAutoContinueEngine(deps) {
|
|
|
1574
2180
|
requested_by_agent_name: run.agentName,
|
|
1575
2181
|
status: effectiveParsedStatus,
|
|
1576
2182
|
artifacts: artifacts.length,
|
|
1577
|
-
decisions:
|
|
1578
|
-
blocking_decisions:
|
|
1579
|
-
non_blocking_decisions:
|
|
2183
|
+
decisions: allDecisions.length,
|
|
2184
|
+
blocking_decisions: normalizedBlockingDecisionCount,
|
|
2185
|
+
non_blocking_decisions: normalizedNonBlockingDecisionCount,
|
|
1580
2186
|
status_updates: statusUpdateResult.applied,
|
|
1581
2187
|
status_updates_buffered: statusUpdateResult.buffered,
|
|
1582
2188
|
reported_skill_evidence_count: skillEvidence.length,
|
|
@@ -1665,13 +2271,13 @@ export function createAutoContinueEngine(deps) {
|
|
|
1665
2271
|
parsed_status: effectiveParsedStatus,
|
|
1666
2272
|
has_output: Boolean(parsed),
|
|
1667
2273
|
artifacts: artifacts.length,
|
|
1668
|
-
decisions:
|
|
1669
|
-
blocking_decisions:
|
|
1670
|
-
non_blocking_decisions:
|
|
2274
|
+
decisions: allDecisions.length,
|
|
2275
|
+
blocking_decisions: normalizedBlockingDecisionCount,
|
|
2276
|
+
non_blocking_decisions: normalizedNonBlockingDecisionCount,
|
|
1671
2277
|
decision_ids: decisionIds,
|
|
1672
2278
|
blocking_decision_ids: Array.from(new Set(blockingDecisionIds)),
|
|
1673
2279
|
non_blocking_decision_ids: Array.from(new Set(nonBlockingDecisionIds)),
|
|
1674
|
-
decision_required: blockingDecisionQueued,
|
|
2280
|
+
decision_required: blockingDecisionQueued || effectiveParsedStatus === "needs_decision",
|
|
1675
2281
|
status_updates_applied: statusUpdateResult.applied,
|
|
1676
2282
|
status_updates_buffered: statusUpdateResult.buffered,
|
|
1677
2283
|
reported_skill_evidence_count: skillEvidence.length,
|
|
@@ -2736,6 +3342,7 @@ export function createAutoContinueEngine(deps) {
|
|
|
2736
3342
|
// Try server KickoffContext (includes team context, acceptance criteria, etc.)
|
|
2737
3343
|
let prompt;
|
|
2738
3344
|
let kickoffContextHash = null;
|
|
3345
|
+
let kickoffRuntimeSettings = null;
|
|
2739
3346
|
if (fetchKickoffContextSafeFn && renderKickoffMessageFn) {
|
|
2740
3347
|
let kickoff = null;
|
|
2741
3348
|
try {
|
|
@@ -2752,6 +3359,7 @@ export function createAutoContinueEngine(deps) {
|
|
|
2752
3359
|
// best effort: fall back to local prompt
|
|
2753
3360
|
}
|
|
2754
3361
|
if (kickoff) {
|
|
3362
|
+
kickoffRuntimeSettings = kickoff.runtime_settings ?? null;
|
|
2755
3363
|
const rendered = renderKickoffMessageFn({
|
|
2756
3364
|
baseMessage: `Execute workstream slice for ${workstreamTitle ?? selectedWorkstreamId}`,
|
|
2757
3365
|
kickoff,
|
|
@@ -2797,6 +3405,7 @@ export function createAutoContinueEngine(deps) {
|
|
|
2797
3405
|
schemaPath,
|
|
2798
3406
|
});
|
|
2799
3407
|
}
|
|
3408
|
+
questionAutoAnswerPolicyByScope.set(questionScopeKey(run.initiativeId, selectedWorkstreamId), normalizeQuestionAutoAnswerPolicy(kickoffRuntimeSettings));
|
|
2800
3409
|
// Append per-scope directive for milestone/workstream scopes.
|
|
2801
3410
|
if (run.scope !== "task") {
|
|
2802
3411
|
const msNodes = scopeMilestoneIds
|
|
@@ -3210,8 +3819,13 @@ export function createAutoContinueEngine(deps) {
|
|
|
3210
3819
|
if (decisionIsBlocking(record))
|
|
3211
3820
|
continue;
|
|
3212
3821
|
const autoApprovalNote = "Auto-approved by OrgX auto-fix (non-blocking follow-up decision).";
|
|
3822
|
+
const autoApprovalSourceClient = normalizeRuntimeSourceClient(process.env.ORGX_AUTOPILOT_EXECUTOR ?? process.env.ORGX_AUTOPILOT_WORKER_KIND);
|
|
3213
3823
|
if (typeof client.decideDecision === "function") {
|
|
3214
|
-
await client.decideDecision(decisionId, "approve", {
|
|
3824
|
+
await client.decideDecision(decisionId, "approve", {
|
|
3825
|
+
note: autoApprovalNote,
|
|
3826
|
+
source_client: autoApprovalSourceClient,
|
|
3827
|
+
sourceClient: autoApprovalSourceClient,
|
|
3828
|
+
});
|
|
3215
3829
|
}
|
|
3216
3830
|
else {
|
|
3217
3831
|
await client.updateEntity("decision", decisionId, {
|