pilotswarm-sdk 0.5.19 → 0.5.21
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/api/src/http-api-transport.js +4 -0
- package/api/src/protocol.js +2 -0
- package/dist/blob-store.d.ts +42 -11
- package/dist/blob-store.d.ts.map +1 -1
- package/dist/blob-store.js +163 -53
- package/dist/blob-store.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +231 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +62 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +81 -4
- package/dist/cms.js.map +1 -1
- package/dist/distiller-tools.d.ts +9 -0
- package/dist/distiller-tools.d.ts.map +1 -0
- package/dist/distiller-tools.js +102 -0
- package/dist/distiller-tools.js.map +1 -0
- package/dist/footprint.d.ts +129 -0
- package/dist/footprint.d.ts.map +1 -0
- package/dist/footprint.js +254 -0
- package/dist/footprint.js.map +1 -0
- package/dist/inspect-tools.d.ts.map +1 -1
- package/dist/inspect-tools.js +63 -2
- package/dist/inspect-tools.js.map +1 -1
- package/dist/managed-session.d.ts.map +1 -1
- package/dist/managed-session.js +140 -14
- package/dist/managed-session.js.map +1 -1
- package/dist/management-client.d.ts +32 -0
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +93 -0
- package/dist/management-client.js.map +1 -1
- package/dist/orchestration/agents.d.ts +18 -0
- package/dist/orchestration/agents.d.ts.map +1 -1
- package/dist/orchestration/agents.js +63 -3
- package/dist/orchestration/agents.js.map +1 -1
- package/dist/orchestration/index.d.ts +2 -2
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/lifecycle.d.ts +1 -0
- package/dist/orchestration/lifecycle.d.ts.map +1 -1
- package/dist/orchestration/lifecycle.js +349 -1
- package/dist/orchestration/lifecycle.js.map +1 -1
- package/dist/orchestration/queue.d.ts.map +1 -1
- package/dist/orchestration/queue.js +61 -2
- package/dist/orchestration/queue.js.map +1 -1
- package/dist/orchestration/runtime.d.ts +1 -1
- package/dist/orchestration/runtime.d.ts.map +1 -1
- package/dist/orchestration/runtime.js +31 -1
- package/dist/orchestration/runtime.js.map +1 -1
- package/dist/orchestration/state.d.ts +29 -0
- package/dist/orchestration/state.d.ts.map +1 -1
- package/dist/orchestration/state.js +11 -0
- package/dist/orchestration/state.js.map +1 -1
- package/dist/orchestration/turn.d.ts.map +1 -1
- package/dist/orchestration/turn.js +68 -1
- package/dist/orchestration/turn.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +8 -2
- package/dist/orchestration-registry.js.map +1 -1
- package/dist/orchestration-version.d.ts +1 -1
- package/dist/orchestration-version.js +1 -1
- package/dist/orchestration.d.ts +2 -2
- package/dist/orchestration.js +1 -1
- package/dist/orchestration_1_0_46.d.ts +1 -1
- package/dist/orchestration_1_0_47.d.ts +1 -1
- package/dist/orchestration_1_0_48.d.ts +1 -1
- package/dist/orchestration_1_0_49.d.ts +1 -1
- package/dist/orchestration_1_0_50.d.ts +1 -1
- package/dist/orchestration_1_0_65/agents.d.ts +43 -0
- package/dist/orchestration_1_0_65/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/agents.js +780 -0
- package/dist/orchestration_1_0_65/agents.js.map +1 -0
- package/dist/orchestration_1_0_65/index.d.ts +24 -0
- package/dist/orchestration_1_0_65/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/index.js +13 -0
- package/dist/orchestration_1_0_65/index.js.map +1 -0
- package/dist/orchestration_1_0_65/lifecycle.d.ts +48 -0
- package/dist/orchestration_1_0_65/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/lifecycle.js +572 -0
- package/dist/orchestration_1_0_65/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_65/queue.d.ts +7 -0
- package/dist/orchestration_1_0_65/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/queue.js +745 -0
- package/dist/orchestration_1_0_65/queue.js.map +1 -0
- package/dist/orchestration_1_0_65/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_65/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/runtime.js +195 -0
- package/dist/orchestration_1_0_65/runtime.js.map +1 -0
- package/dist/orchestration_1_0_65/state.d.ts +147 -0
- package/dist/orchestration_1_0_65/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/state.js +147 -0
- package/dist/orchestration_1_0_65/state.js.map +1 -0
- package/dist/orchestration_1_0_65/turn.d.ts +34 -0
- package/dist/orchestration_1_0_65/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/turn.js +1144 -0
- package/dist/orchestration_1_0_65/turn.js.map +1 -0
- package/dist/orchestration_1_0_65/utils.d.ts +39 -0
- package/dist/orchestration_1_0_65/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_65/utils.js +316 -0
- package/dist/orchestration_1_0_65/utils.js.map +1 -0
- package/dist/orchestration_1_0_66/agents.d.ts +52 -0
- package/dist/orchestration_1_0_66/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/agents.js +825 -0
- package/dist/orchestration_1_0_66/agents.js.map +1 -0
- package/dist/orchestration_1_0_66/index.d.ts +24 -0
- package/dist/orchestration_1_0_66/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/index.js +13 -0
- package/dist/orchestration_1_0_66/index.js.map +1 -0
- package/dist/orchestration_1_0_66/lifecycle.d.ts +48 -0
- package/dist/orchestration_1_0_66/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/lifecycle.js +573 -0
- package/dist/orchestration_1_0_66/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_66/queue.d.ts +7 -0
- package/dist/orchestration_1_0_66/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/queue.js +776 -0
- package/dist/orchestration_1_0_66/queue.js.map +1 -0
- package/dist/orchestration_1_0_66/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_66/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/runtime.js +198 -0
- package/dist/orchestration_1_0_66/runtime.js.map +1 -0
- package/dist/orchestration_1_0_66/state.d.ts +149 -0
- package/dist/orchestration_1_0_66/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/state.js +148 -0
- package/dist/orchestration_1_0_66/state.js.map +1 -0
- package/dist/orchestration_1_0_66/turn.d.ts +34 -0
- package/dist/orchestration_1_0_66/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/turn.js +1169 -0
- package/dist/orchestration_1_0_66/turn.js.map +1 -0
- package/dist/orchestration_1_0_66/utils.d.ts +39 -0
- package/dist/orchestration_1_0_66/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_66/utils.js +316 -0
- package/dist/orchestration_1_0_66/utils.js.map +1 -0
- package/dist/orchestration_1_0_67/agents.d.ts +52 -0
- package/dist/orchestration_1_0_67/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/agents.js +825 -0
- package/dist/orchestration_1_0_67/agents.js.map +1 -0
- package/dist/orchestration_1_0_67/index.d.ts +28 -0
- package/dist/orchestration_1_0_67/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/index.js +13 -0
- package/dist/orchestration_1_0_67/index.js.map +1 -0
- package/dist/orchestration_1_0_67/lifecycle.d.ts +49 -0
- package/dist/orchestration_1_0_67/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/lifecycle.js +807 -0
- package/dist/orchestration_1_0_67/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_67/queue.d.ts +7 -0
- package/dist/orchestration_1_0_67/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/queue.js +804 -0
- package/dist/orchestration_1_0_67/queue.js.map +1 -0
- package/dist/orchestration_1_0_67/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_67/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/runtime.js +228 -0
- package/dist/orchestration_1_0_67/runtime.js.map +1 -0
- package/dist/orchestration_1_0_67/state.d.ts +176 -0
- package/dist/orchestration_1_0_67/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/state.js +158 -0
- package/dist/orchestration_1_0_67/state.js.map +1 -0
- package/dist/orchestration_1_0_67/turn.d.ts +34 -0
- package/dist/orchestration_1_0_67/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/turn.js +1208 -0
- package/dist/orchestration_1_0_67/turn.js.map +1 -0
- package/dist/orchestration_1_0_67/utils.d.ts +39 -0
- package/dist/orchestration_1_0_67/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_67/utils.js +316 -0
- package/dist/orchestration_1_0_67/utils.js.map +1 -0
- package/dist/regen-worker.d.ts +167 -0
- package/dist/regen-worker.d.ts.map +1 -0
- package/dist/regen-worker.js +512 -0
- package/dist/regen-worker.js.map +1 -0
- package/dist/resourcemgr-tools.d.ts.map +1 -1
- package/dist/resourcemgr-tools.js +33 -2
- package/dist/resourcemgr-tools.js.map +1 -1
- package/dist/session-lifecycle.d.ts +8 -0
- package/dist/session-lifecycle.d.ts.map +1 -1
- package/dist/session-lifecycle.js +35 -17
- package/dist/session-lifecycle.js.map +1 -1
- package/dist/session-manager.d.ts +21 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +104 -11
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +34 -0
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +407 -5
- package/dist/session-proxy.js.map +1 -1
- package/dist/session-store.d.ts +50 -15
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +143 -30
- package/dist/session-store.js.map +1 -1
- package/dist/snapshot-protocol.d.ts +34 -3
- package/dist/snapshot-protocol.d.ts.map +1 -1
- package/dist/snapshot-protocol.js +25 -0
- package/dist/snapshot-protocol.js.map +1 -1
- package/dist/types.d.ts +101 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web/web-management-client.d.ts +11 -0
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +8 -0
- package/dist/web/web-management-client.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +7 -0
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import { describeCronAt } from "../cron-at.js";
|
|
2
|
+
import { normalizeMessageSender } from "../message-sender.js";
|
|
3
|
+
import { COMMAND_VERSION_KEY, RESPONSE_LATEST_KEY, RESPONSE_VERSION_KEY, commandResponseKey, } from "../types.js";
|
|
4
|
+
import { createSessionProxy } from "../session-proxy.js";
|
|
5
|
+
import { beginGracefulShutdown, } from "./agents.js";
|
|
6
|
+
import { FIRST_SUMMARIZE_DELAY, INTERNAL_SYSTEM_TURN_PROMPT, REPEAT_SUMMARIZE_DELAY, } from "./state.js";
|
|
7
|
+
import { appendSystemContext, extractPromptSystemContext, mergePrompt, } from "./utils.js";
|
|
8
|
+
// ─── Custom status / KV helpers ─────────────────────────────
|
|
9
|
+
export function publishStatus(runtime, status, extra = {}) {
|
|
10
|
+
const { state } = runtime;
|
|
11
|
+
const signal = {
|
|
12
|
+
status,
|
|
13
|
+
iteration: state.iteration,
|
|
14
|
+
...(state.lastResponseVersion > 0 ? { responseVersion: state.lastResponseVersion } : {}),
|
|
15
|
+
...(state.lastCommandVersion > 0 ? { commandVersion: state.lastCommandVersion } : {}),
|
|
16
|
+
...(state.lastCommandId ? { commandId: state.lastCommandId } : {}),
|
|
17
|
+
...(state.cronAtSchedule
|
|
18
|
+
? {
|
|
19
|
+
cronActive: true,
|
|
20
|
+
cronKind: "wall-clock",
|
|
21
|
+
cronReason: state.cronAtSchedule.reason,
|
|
22
|
+
cronNextFireAt: state.cronAtSchedule.nextFireAtMs,
|
|
23
|
+
cronTimezone: state.cronAtSchedule.tz,
|
|
24
|
+
cronMaxFires: state.cronAtSchedule.maxFires,
|
|
25
|
+
cronFiresCompleted: state.cronAtSchedule.firesCompleted,
|
|
26
|
+
}
|
|
27
|
+
: state.cronSchedule
|
|
28
|
+
? {
|
|
29
|
+
cronActive: true,
|
|
30
|
+
cronKind: "interval",
|
|
31
|
+
cronInterval: state.cronSchedule.intervalSeconds,
|
|
32
|
+
cronReason: state.cronSchedule.reason,
|
|
33
|
+
}
|
|
34
|
+
: { cronActive: false }),
|
|
35
|
+
...(state.contextUsage ? { contextUsage: state.contextUsage } : {}),
|
|
36
|
+
...extra,
|
|
37
|
+
};
|
|
38
|
+
runtime.ctx.setCustomStatus(JSON.stringify(signal));
|
|
39
|
+
}
|
|
40
|
+
function writeJsonValue(ctx, key, value) {
|
|
41
|
+
ctx.setValue(key, JSON.stringify(value));
|
|
42
|
+
}
|
|
43
|
+
export function readCounter(ctx, key) {
|
|
44
|
+
const raw = ctx.getValue(key);
|
|
45
|
+
if (raw == null)
|
|
46
|
+
return 0;
|
|
47
|
+
const parsed = Number(raw);
|
|
48
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
49
|
+
}
|
|
50
|
+
function bumpCounter(ctx, key) {
|
|
51
|
+
const next = readCounter(ctx, key) + 1;
|
|
52
|
+
ctx.setValue(key, String(next));
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
export function* writeLatestResponse(runtime, payload) {
|
|
56
|
+
const version = bumpCounter(runtime.ctx, RESPONSE_VERSION_KEY);
|
|
57
|
+
const emittedAt = yield runtime.ctx.utcNow();
|
|
58
|
+
const responsePayload = {
|
|
59
|
+
schemaVersion: 1,
|
|
60
|
+
version,
|
|
61
|
+
emittedAt,
|
|
62
|
+
...payload,
|
|
63
|
+
};
|
|
64
|
+
writeJsonValue(runtime.ctx, RESPONSE_LATEST_KEY, responsePayload);
|
|
65
|
+
runtime.state.lastResponseVersion = version;
|
|
66
|
+
return responsePayload;
|
|
67
|
+
}
|
|
68
|
+
export function* writeCommandResponse(runtime, response) {
|
|
69
|
+
const version = bumpCounter(runtime.ctx, COMMAND_VERSION_KEY);
|
|
70
|
+
const emittedAt = yield runtime.ctx.utcNow();
|
|
71
|
+
const payload = {
|
|
72
|
+
...response,
|
|
73
|
+
schemaVersion: 1,
|
|
74
|
+
version,
|
|
75
|
+
emittedAt,
|
|
76
|
+
};
|
|
77
|
+
writeJsonValue(runtime.ctx, commandResponseKey(response.id), payload);
|
|
78
|
+
runtime.state.lastCommandVersion = version;
|
|
79
|
+
runtime.state.lastCommandId = response.id;
|
|
80
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
81
|
+
eventType: "session.command_completed",
|
|
82
|
+
data: { cmd: response.cmd, id: response.id },
|
|
83
|
+
}]);
|
|
84
|
+
return payload;
|
|
85
|
+
}
|
|
86
|
+
// ─── Hydration / dehydration / checkpointing ────────────────
|
|
87
|
+
export function wrapWithResumeContext(runtime, userPrompt, extra) {
|
|
88
|
+
const base = runtime.state.pendingRehydrationMessage ??
|
|
89
|
+
`The session was dehydrated and has been rehydrated on a new worker. ` +
|
|
90
|
+
`The LLM conversation history is preserved.`;
|
|
91
|
+
runtime.state.pendingRehydrationMessage = undefined;
|
|
92
|
+
const parts = [userPrompt, ``, `[SYSTEM: ${base}`];
|
|
93
|
+
if (extra)
|
|
94
|
+
parts.push(extra);
|
|
95
|
+
parts.push(`]`);
|
|
96
|
+
return parts.join("\n");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Session lifecycle protocol (§3.4 tier 3): release the worker affinity by
|
|
100
|
+
* rotating the GUID — a pure orchestration-state change, no activity at all.
|
|
101
|
+
* Nothing needs uploading (every completed turn committed its snapshot
|
|
102
|
+
* inside the runTurn activity) and nothing needs telling the old worker:
|
|
103
|
+
* its local copy is a cache reclaimed by its own eviction clock. The next
|
|
104
|
+
* event hydrates wherever duroxide places the new key.
|
|
105
|
+
*/
|
|
106
|
+
export function* releaseAffinity(runtime, reason, eventData) {
|
|
107
|
+
const { ctx, state } = runtime;
|
|
108
|
+
ctx.traceInfo(`[orch] releasing worker affinity (reason=${reason})`);
|
|
109
|
+
state.activeTimer = null;
|
|
110
|
+
state.affinityKey = yield ctx.newGuid();
|
|
111
|
+
runtime.session = createSessionProxy(ctx, runtime.input.sessionId, state.affinityKey, state.config);
|
|
112
|
+
try {
|
|
113
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
114
|
+
eventType: "session.affinity_released",
|
|
115
|
+
data: { reason, snapshotVersion: state.snapshotVersion, ...(eventData ?? {}) },
|
|
116
|
+
}]);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
ctx.traceInfo(`[orch] affinity_released event failed (non-fatal): ${err.message ?? err}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function* maybeSummarize(runtime) {
|
|
123
|
+
if (runtime.options.isSystem)
|
|
124
|
+
return;
|
|
125
|
+
const now = yield runtime.ctx.utcNow();
|
|
126
|
+
if (runtime.state.nextSummarizeAt === 0) {
|
|
127
|
+
runtime.state.nextSummarizeAt = now + FIRST_SUMMARIZE_DELAY;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (now < runtime.state.nextSummarizeAt)
|
|
131
|
+
return;
|
|
132
|
+
try {
|
|
133
|
+
runtime.ctx.traceInfo(`[orch] summarizing session title`);
|
|
134
|
+
yield runtime.manager.summarizeSession(runtime.input.sessionId);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
runtime.ctx.traceInfo(`[orch] summarize failed: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
runtime.state.nextSummarizeAt = now + REPEAT_SUMMARIZE_DELAY;
|
|
140
|
+
}
|
|
141
|
+
// ─── Task context / cron action helpers ─────────────────────
|
|
142
|
+
export function ensureTaskContext(runtime, sourcePrompt) {
|
|
143
|
+
if (runtime.state.taskContext || !sourcePrompt)
|
|
144
|
+
return;
|
|
145
|
+
runtime.state.taskContext = sourcePrompt.slice(0, 2000);
|
|
146
|
+
const base = typeof runtime.options.baseSystemMessage === "string"
|
|
147
|
+
? runtime.options.baseSystemMessage ?? ""
|
|
148
|
+
: runtime.options.baseSystemMessage?.content ?? "";
|
|
149
|
+
runtime.state.config.systemMessage = base + (base ? "\n\n" : "") +
|
|
150
|
+
"[RECURRING TASK]\n" +
|
|
151
|
+
"Original user request (always remember, even if conversation history is truncated):\n\"" +
|
|
152
|
+
runtime.state.taskContext + "\"";
|
|
153
|
+
}
|
|
154
|
+
export function applyCronAction(runtime, action, sourcePrompt) {
|
|
155
|
+
runtime.state.interruptedCronTimer = null;
|
|
156
|
+
if (action.action === "cancel") {
|
|
157
|
+
runtime.ctx.traceInfo("[orch] cron cancelled");
|
|
158
|
+
runtime.state.cronSchedule = undefined;
|
|
159
|
+
runtime.state.cronAtSchedule = undefined;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
ensureTaskContext(runtime, sourcePrompt);
|
|
163
|
+
runtime.state.cronAtSchedule = undefined;
|
|
164
|
+
runtime.state.cronSchedule = {
|
|
165
|
+
intervalSeconds: action.intervalSeconds,
|
|
166
|
+
reason: action.reason,
|
|
167
|
+
};
|
|
168
|
+
runtime.ctx.traceInfo(`[orch] cron scheduled: every ${action.intervalSeconds}s (${action.reason})`);
|
|
169
|
+
}
|
|
170
|
+
export function* applyCronAtAction(runtime, action, sourcePrompt) {
|
|
171
|
+
runtime.state.interruptedCronTimer = null;
|
|
172
|
+
if (action.action === "cancel") {
|
|
173
|
+
runtime.ctx.traceInfo("[orch] cron_at cancelled");
|
|
174
|
+
runtime.state.cronAtSchedule = undefined;
|
|
175
|
+
runtime.state.cronSchedule = undefined;
|
|
176
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
177
|
+
eventType: "session.cron_at_cancelled",
|
|
178
|
+
data: {},
|
|
179
|
+
}]);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
ensureTaskContext(runtime, sourcePrompt);
|
|
183
|
+
const afterUtcMs = yield runtime.ctx.utcNow();
|
|
184
|
+
const nextFire = yield runtime.manager.computeCronAtNextFire(action.schedule, afterUtcMs, action.schedule.lastOccurrenceKey);
|
|
185
|
+
runtime.state.cronSchedule = undefined;
|
|
186
|
+
runtime.state.cronAtSchedule = {
|
|
187
|
+
...action.schedule,
|
|
188
|
+
firesCompleted: action.schedule.firesCompleted ?? 0,
|
|
189
|
+
nextFireAtMs: nextFire.nextFireAtMs,
|
|
190
|
+
nextOccurrenceKey: nextFire.occurrenceKey,
|
|
191
|
+
};
|
|
192
|
+
runtime.ctx.traceInfo(`[orch] cron_at scheduled: ${describeCronAt(runtime.state.cronAtSchedule)} (${runtime.state.cronAtSchedule.reason})`);
|
|
193
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
194
|
+
eventType: "session.cron_at_scheduled",
|
|
195
|
+
data: {
|
|
196
|
+
...runtime.state.cronAtSchedule,
|
|
197
|
+
nextFireAt: new Date(nextFire.nextFireAtMs).toISOString(),
|
|
198
|
+
localTime: nextFire.localTime,
|
|
199
|
+
skippedOccurrences: nextFire.skippedOccurrences,
|
|
200
|
+
},
|
|
201
|
+
}]);
|
|
202
|
+
}
|
|
203
|
+
export function* drainLeadingQueuedScheduleActions(runtime, sourcePrompt) {
|
|
204
|
+
while (runtime.state.pendingToolActions[0]?.type === "cron" || runtime.state.pendingToolActions[0]?.type === "cron_at") {
|
|
205
|
+
const action = runtime.state.pendingToolActions.shift();
|
|
206
|
+
if (action.type === "cron") {
|
|
207
|
+
applyCronAction(runtime, action, sourcePrompt);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
yield* applyCronAtAction(runtime, action, sourcePrompt);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export const drainLeadingQueuedCronActions = drainLeadingQueuedScheduleActions;
|
|
215
|
+
// ─── Cancellation tombstone helpers ─────────────────────────
|
|
216
|
+
export function promptIdsIntersectCancellation(runtime, ids) {
|
|
217
|
+
return ids.length > 0 && ids.some((id) => runtime.state.cancelledMessageIds.has(id));
|
|
218
|
+
}
|
|
219
|
+
export function* recordCancelledMessageIds(runtime, ids, reason) {
|
|
220
|
+
const nextIds = ids.filter((id) => id && !runtime.state.emittedCancelledMessageIds.has(id));
|
|
221
|
+
if (nextIds.length === 0)
|
|
222
|
+
return;
|
|
223
|
+
for (const id of nextIds)
|
|
224
|
+
runtime.state.emittedCancelledMessageIds.add(id);
|
|
225
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
226
|
+
eventType: "pending_messages.cancelled",
|
|
227
|
+
data: {
|
|
228
|
+
clientMessageIds: nextIds,
|
|
229
|
+
reason,
|
|
230
|
+
},
|
|
231
|
+
}]);
|
|
232
|
+
}
|
|
233
|
+
// ─── Pending child-digest accumulation ──────────────────────
|
|
234
|
+
export function bufferChildUpdate(runtime, update, observedAtMs) {
|
|
235
|
+
if (!runtime.state.pendingChildDigest) {
|
|
236
|
+
runtime.state.pendingChildDigest = {
|
|
237
|
+
startedAtMs: observedAtMs,
|
|
238
|
+
updates: [],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const nextEntry = {
|
|
242
|
+
sessionId: update.sessionId,
|
|
243
|
+
updateType: update.updateType,
|
|
244
|
+
...(update.content ? { content: update.content.slice(0, 2000) } : {}),
|
|
245
|
+
...(update.cycleOrigin ? { cycleOrigin: update.cycleOrigin } : {}),
|
|
246
|
+
...(update.cycleStatus ? { cycleStatus: update.cycleStatus } : {}),
|
|
247
|
+
...(update.verdict ? { verdict: update.verdict } : {}),
|
|
248
|
+
observedAtMs,
|
|
249
|
+
};
|
|
250
|
+
const existingIndex = runtime.state.pendingChildDigest.updates.findIndex((entry) => entry.sessionId === update.sessionId);
|
|
251
|
+
if (existingIndex >= 0) {
|
|
252
|
+
runtime.state.pendingChildDigest.updates[existingIndex] = nextEntry;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
runtime.state.pendingChildDigest.updates.push(nextEntry);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export function clearPendingChildDigest(runtime) {
|
|
259
|
+
runtime.state.pendingChildDigest = null;
|
|
260
|
+
}
|
|
261
|
+
export function buildPendingChildDigestSystemPrompt(runtime) {
|
|
262
|
+
const digest = runtime.state.pendingChildDigest;
|
|
263
|
+
if (!digest || digest.updates.length === 0)
|
|
264
|
+
return undefined;
|
|
265
|
+
const lines = digest.updates.map((update) => {
|
|
266
|
+
const agent = runtime.state.subAgents.find((entry) => entry.sessionId === update.sessionId);
|
|
267
|
+
const label = agent?.orchId ?? update.sessionId;
|
|
268
|
+
const task = agent?.task ? `Task: "${agent.task.slice(0, 120)}"\n` : "";
|
|
269
|
+
const status = agent?.status ?? update.updateType;
|
|
270
|
+
const resultText = String(update.content || agent?.result || "").trim();
|
|
271
|
+
const result = resultText ? resultText.slice(0, 240) : "(no summary)";
|
|
272
|
+
return ` - Agent ${label}\n` +
|
|
273
|
+
` ${task}` +
|
|
274
|
+
` Update: ${update.updateType}\n` +
|
|
275
|
+
` Status: ${status}\n` +
|
|
276
|
+
` Result: ${result}`;
|
|
277
|
+
});
|
|
278
|
+
return `Buffered child updates arrived during the last 30 seconds:\n${lines.join("\n")}\nReview the updates and continue your task.`;
|
|
279
|
+
}
|
|
280
|
+
export function flushPendingChildDigestIntoPrompt(runtime, rawPrompt) {
|
|
281
|
+
const childDigestPrompt = buildPendingChildDigestSystemPrompt(runtime);
|
|
282
|
+
if (!childDigestPrompt)
|
|
283
|
+
return rawPrompt;
|
|
284
|
+
clearPendingChildDigest(runtime);
|
|
285
|
+
return appendSystemContext(rawPrompt, childDigestPrompt);
|
|
286
|
+
}
|
|
287
|
+
// ─── Followup queueing ──────────────────────────────────────
|
|
288
|
+
export function queueFollowup(runtime, nextPrompt) {
|
|
289
|
+
let text = nextPrompt;
|
|
290
|
+
const trimmed = text.trim();
|
|
291
|
+
if (trimmed.startsWith("[SYSTEM:") && trimmed.endsWith("]")) {
|
|
292
|
+
text = trimmed.slice("[SYSTEM:".length, -1).trim();
|
|
293
|
+
}
|
|
294
|
+
runtime.state.pendingPrompt = mergePrompt(runtime.state.pendingPrompt, text);
|
|
295
|
+
}
|
|
296
|
+
// ─── Continue-as-new construction ───────────────────────────
|
|
297
|
+
export function buildContinueInput(runtime, overrides = {}) {
|
|
298
|
+
const { state, options, input } = runtime;
|
|
299
|
+
const { prompt: overridePrompt, requiredTool: overrideRequiredTool, systemPrompt: overrideSystemPrompt, cycleOrigin: overrideCycleOrigin, bootstrapPrompt: overrideBootstrapPrompt, rehydrationMessage: overrideRehydrationMessage, ...restOverrides } = overrides;
|
|
300
|
+
const carriedPrompt = overridePrompt ?? state.pendingPrompt;
|
|
301
|
+
const carriedRequiredTool = overrideRequiredTool ?? state.pendingRequiredTool;
|
|
302
|
+
const carriedSystemPrompt = overrideSystemPrompt ?? state.pendingSystemPrompt;
|
|
303
|
+
const carriedCycleOrigin = overrideCycleOrigin ?? state.pendingCycleOrigin;
|
|
304
|
+
const carriedRehydrationMessage = overrideRehydrationMessage ?? state.pendingRehydrationMessage;
|
|
305
|
+
const promptForInput = carriedPrompt
|
|
306
|
+
?? (carriedSystemPrompt ? INTERNAL_SYSTEM_TURN_PROMPT : undefined);
|
|
307
|
+
const bootstrapForInput = overrideBootstrapPrompt
|
|
308
|
+
?? (carriedPrompt ? state.bootstrapPrompt : carriedSystemPrompt ? true : undefined);
|
|
309
|
+
return {
|
|
310
|
+
sessionId: input.sessionId,
|
|
311
|
+
config: state.config,
|
|
312
|
+
iteration: state.iteration,
|
|
313
|
+
affinityKey: state.affinityKey,
|
|
314
|
+
preserveAffinityOnHydrate: state.preserveAffinityOnHydrate,
|
|
315
|
+
needsHydration: state.needsHydration,
|
|
316
|
+
snapshotVersion: state.snapshotVersion,
|
|
317
|
+
blobEnabled: state.blobEnabled,
|
|
318
|
+
idleTimeout: options.idleTimeout,
|
|
319
|
+
inputGracePeriod: options.inputGracePeriod,
|
|
320
|
+
...(carriedRehydrationMessage ? { rehydrationMessage: carriedRehydrationMessage } : {}),
|
|
321
|
+
nextSummarizeAt: state.nextSummarizeAt,
|
|
322
|
+
taskContext: state.taskContext,
|
|
323
|
+
baseSystemMessage: options.baseSystemMessage,
|
|
324
|
+
...(state.blockedError ? { blockedError: { ...state.blockedError } } : {}),
|
|
325
|
+
...(state.cronSchedule ? { cronSchedule: state.cronSchedule } : {}),
|
|
326
|
+
...(state.cronAtSchedule ? { cronAtSchedule: state.cronAtSchedule } : {}),
|
|
327
|
+
...(state.contextUsage ? { contextUsage: state.contextUsage } : {}),
|
|
328
|
+
...(state.recentClientMessageIds?.length ? { recentClientMessageIds: [...state.recentClientMessageIds] } : {}),
|
|
329
|
+
...(carriedSystemPrompt ? { systemPrompt: carriedSystemPrompt } : {}),
|
|
330
|
+
...(state.runtimeModelNotice ? { runtimeModelNotice: state.runtimeModelNotice } : {}),
|
|
331
|
+
...(promptForInput ? { prompt: promptForInput } : {}),
|
|
332
|
+
// Attachment refs ride the carried prompt — without this, any prompt
|
|
333
|
+
// consumed into pendingPrompt across continue-as-new silently became
|
|
334
|
+
// text-only (the string-only carry predates image attachments).
|
|
335
|
+
...(promptForInput && state.pendingAttachments?.length
|
|
336
|
+
? { attachments: [...state.pendingAttachments] }
|
|
337
|
+
: {}),
|
|
338
|
+
...(carriedRequiredTool ? { requiredTool: carriedRequiredTool } : {}),
|
|
339
|
+
...(carriedCycleOrigin ? { cycleOrigin: carriedCycleOrigin } : {}),
|
|
340
|
+
...(promptForInput && bootstrapForInput !== undefined ? { bootstrapPrompt: bootstrapForInput } : {}),
|
|
341
|
+
subAgents: state.subAgents,
|
|
342
|
+
...(state.reportedFirstCompletionToParent ? { reportedFirstCompletionToParent: true } : {}),
|
|
343
|
+
...(state.pendingToolActions.length > 0 ? { pendingToolActions: state.pendingToolActions } : {}),
|
|
344
|
+
// Session regeneration (1.0.67): epoch + in-flight pipeline state ride
|
|
345
|
+
// EVERY continue-as-new (forced CANs included) or they silently drop.
|
|
346
|
+
transcriptEpoch: state.transcriptEpoch,
|
|
347
|
+
...(state.epochStartPending ? { epochStartPending: true } : {}),
|
|
348
|
+
...(state.regen ? { regen: state.regen } : {}),
|
|
349
|
+
...(state.pendingEpochCommit ? { pendingEpochCommit: state.pendingEpochCommit } : {}),
|
|
350
|
+
...(state.epochStartIteration ? { epochStartIteration: state.epochStartIteration } : {}),
|
|
351
|
+
...(state.lastRegenAtMs ? { lastRegenAtMs: state.lastRegenAtMs } : {}),
|
|
352
|
+
parentSessionId: options.parentSessionId,
|
|
353
|
+
nestingLevel: options.nestingLevel,
|
|
354
|
+
...(options.isSystem ? { isSystem: true } : {}),
|
|
355
|
+
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
356
|
+
retryCount: 0,
|
|
357
|
+
...(state.pendingInputQuestion ? { pendingInputQuestion: state.pendingInputQuestion } : {}),
|
|
358
|
+
...(state.waitingForAgentIds ? { waitingForAgentIds: state.waitingForAgentIds } : {}),
|
|
359
|
+
...(state.interruptedWaitTimer ? { interruptedWaitTimer: state.interruptedWaitTimer } : {}),
|
|
360
|
+
...(state.interruptedCronTimer ? { interruptedCronTimer: state.interruptedCronTimer } : {}),
|
|
361
|
+
...(state.pendingChildDigest ? { pendingChildDigest: state.pendingChildDigest } : {}),
|
|
362
|
+
...(state.pendingShutdown ? { pendingShutdown: state.pendingShutdown } : {}),
|
|
363
|
+
// Multi-writer attribution posture (security model): carried so a
|
|
364
|
+
// shared session stays attributed across continue-as-new. Omitted
|
|
365
|
+
// entirely for single-writer sessions, keeping the CAN input
|
|
366
|
+
// byte-identical to pre-sender builds.
|
|
367
|
+
...(state.observedSenderKeys?.length ? { observedSenderKeys: [...state.observedSenderKeys] } : {}),
|
|
368
|
+
...(state.multiWriter ? { multiWriter: true } : {}),
|
|
369
|
+
...(state.sharedPreambleSent ? { sharedPreambleSent: true } : {}),
|
|
370
|
+
...(state.ownerDisplay ? { ownerDisplay: state.ownerDisplay } : {}),
|
|
371
|
+
...restOverrides,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
export function buildContinueInputWithPrompt(runtime, nextPrompt, overrides = {}) {
|
|
375
|
+
const extracted = extractPromptSystemContext(nextPrompt);
|
|
376
|
+
const mergedPrompt = mergePrompt(runtime.state.pendingPrompt, extracted.prompt);
|
|
377
|
+
const mergedSystemPrompt = mergePrompt(runtime.state.pendingSystemPrompt, extracted.systemPrompt);
|
|
378
|
+
return buildContinueInput(runtime, {
|
|
379
|
+
...(mergedPrompt ? { prompt: mergedPrompt } : {}),
|
|
380
|
+
...(mergedSystemPrompt ? { systemPrompt: mergedSystemPrompt } : {}),
|
|
381
|
+
...overrides,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/** Capture the active timer state into a continueAsNew input and yield the version-bumped CAN. */
|
|
385
|
+
export function* versionedContinueAsNew(runtime, canInput) {
|
|
386
|
+
const { state } = runtime;
|
|
387
|
+
if (state.activeTimer) {
|
|
388
|
+
const now = yield runtime.ctx.utcNow();
|
|
389
|
+
const remainingMs = Math.max(0, state.activeTimer.deadlineMs - now);
|
|
390
|
+
canInput.activeTimerState = {
|
|
391
|
+
remainingMs,
|
|
392
|
+
reason: state.activeTimer.reason,
|
|
393
|
+
type: state.activeTimer.type,
|
|
394
|
+
originalDurationMs: state.activeTimer.originalDurationMs,
|
|
395
|
+
...(state.activeTimer.shouldRehydrate ? { shouldRehydrate: true } : {}),
|
|
396
|
+
...(state.activeTimer.waitPlan ? { waitPlan: state.activeTimer.waitPlan } : {}),
|
|
397
|
+
...(state.activeTimer.content ? { content: state.activeTimer.content } : {}),
|
|
398
|
+
...(state.activeTimer.question ? { question: state.activeTimer.question } : {}),
|
|
399
|
+
...(state.activeTimer.choices ? { choices: state.activeTimer.choices } : {}),
|
|
400
|
+
...(state.activeTimer.allowFreeform !== undefined ? { allowFreeform: state.activeTimer.allowFreeform } : {}),
|
|
401
|
+
...(state.activeTimer.agentIds ? { agentIds: state.activeTimer.agentIds } : {}),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
// Lifecycle protocol: no checkpoint before a warm CAN — every turn
|
|
405
|
+
// already committed its snapshot inside the runTurn activity, so a CAN
|
|
406
|
+
// carries no undurable state.
|
|
407
|
+
canInput.sourceOrchestrationVersion = runtime.versions.currentVersion;
|
|
408
|
+
yield runtime.ctx.continueAsNewVersioned(canInput, runtime.versions.latestVersion);
|
|
409
|
+
}
|
|
410
|
+
export function continueInput(runtime, overrides = {}) {
|
|
411
|
+
return buildContinueInput(runtime, overrides);
|
|
412
|
+
}
|
|
413
|
+
export function continueInputWithPrompt(runtime, nextPrompt, overrides = {}) {
|
|
414
|
+
return buildContinueInputWithPrompt(runtime, nextPrompt, overrides);
|
|
415
|
+
}
|
|
416
|
+
// ─── Command handling ───────────────────────────────────────
|
|
417
|
+
export function* handleCommand(runtime, cmdMsg) {
|
|
418
|
+
runtime.ctx.traceInfo(`[orch-cmd] ${cmdMsg.cmd} id=${cmdMsg.id}`);
|
|
419
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
420
|
+
eventType: "session.command_received",
|
|
421
|
+
data: { cmd: cmdMsg.cmd, id: cmdMsg.id },
|
|
422
|
+
}]);
|
|
423
|
+
switch (cmdMsg.cmd) {
|
|
424
|
+
case "set_model": {
|
|
425
|
+
const newModel = String(cmdMsg.args?.model || "").trim();
|
|
426
|
+
if (!newModel) {
|
|
427
|
+
const resp = {
|
|
428
|
+
id: cmdMsg.id,
|
|
429
|
+
cmd: cmdMsg.cmd,
|
|
430
|
+
error: "set_model requires a non-empty model",
|
|
431
|
+
};
|
|
432
|
+
yield* writeCommandResponse(runtime, resp);
|
|
433
|
+
publishStatus(runtime, "idle");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const oldModel = runtime.state.config.model || "(default)";
|
|
437
|
+
const hasEffort = cmdMsg.args?.reasoningEffort !== undefined;
|
|
438
|
+
const oldEffort = runtime.state.config.reasoningEffort ?? null;
|
|
439
|
+
const newEffort = hasEffort
|
|
440
|
+
? (cmdMsg.args?.reasoningEffort ? String(cmdMsg.args.reasoningEffort) : null)
|
|
441
|
+
: oldEffort;
|
|
442
|
+
// Context-window tier is applied like reasoning effort: omitted from
|
|
443
|
+
// the args → preserve the session's current tier; present → apply it
|
|
444
|
+
// (the next turn rebinds the model, requiresModelRebind() sees the
|
|
445
|
+
// change). Durable via the checkpointed orchestration config.
|
|
446
|
+
const hasContextTier = cmdMsg.args?.contextTier !== undefined;
|
|
447
|
+
const oldContextTier = runtime.state.config.contextTier ?? null;
|
|
448
|
+
const newContextTier = hasContextTier
|
|
449
|
+
? (cmdMsg.args?.contextTier ? String(cmdMsg.args.contextTier) : null)
|
|
450
|
+
: oldContextTier;
|
|
451
|
+
runtime.state.config = {
|
|
452
|
+
...runtime.state.config,
|
|
453
|
+
model: newModel,
|
|
454
|
+
...(hasEffort ? { reasoningEffort: (newEffort ?? undefined) } : {}),
|
|
455
|
+
...(hasContextTier ? { contextTier: (newContextTier ?? undefined) } : {}),
|
|
456
|
+
};
|
|
457
|
+
const newModelLabel = newEffort ? `${newModel}:${newEffort}` : newModel;
|
|
458
|
+
runtime.state.runtimeModelNotice = `Runtime model for this turn is ${newModelLabel}. If asked what model you are using, answer this value.`;
|
|
459
|
+
yield* captureModelSwitchInterruptedTimer(runtime, newModelLabel);
|
|
460
|
+
yield runtime.manager.updateSessionModel(runtime.input.sessionId, newModel, newEffort);
|
|
461
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
462
|
+
eventType: "session.model_changed",
|
|
463
|
+
data: { oldModel, newModel, oldReasoningEffort: oldEffort, newReasoningEffort: newEffort, oldContextTier, newContextTier, source: cmdMsg.args?.source ?? "user" },
|
|
464
|
+
}]);
|
|
465
|
+
const resp = {
|
|
466
|
+
id: cmdMsg.id,
|
|
467
|
+
cmd: cmdMsg.cmd,
|
|
468
|
+
result: { ok: true, oldModel, newModel, oldReasoningEffort: oldEffort, newReasoningEffort: newEffort, oldContextTier, newContextTier, appliesOn: "next_turn" },
|
|
469
|
+
};
|
|
470
|
+
yield* writeCommandResponse(runtime, resp);
|
|
471
|
+
publishStatus(runtime, "idle");
|
|
472
|
+
yield* versionedContinueAsNew(runtime, continueInputWithPrompt(runtime, `Continue on ${newModelLabel}.`, {
|
|
473
|
+
bootstrapPrompt: true,
|
|
474
|
+
}));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
case "regenerate": {
|
|
478
|
+
const state = runtime.state;
|
|
479
|
+
const nowMs = yield runtime.ctx.utcNow();
|
|
480
|
+
const source = String(cmdMsg.args?.source ?? "operator");
|
|
481
|
+
// Operator force: an explicit operator action (button/API/MCP, gated
|
|
482
|
+
// session:manage) may bypass the SOFT rate limits (min-age; cooldown
|
|
483
|
+
// is already operator-exempt below). It never bypasses the hard
|
|
484
|
+
// gates: a regen already in flight, or a system session.
|
|
485
|
+
const force = cmdMsg.args?.force === true && source === "operator";
|
|
486
|
+
const refuse = function* (reason) {
|
|
487
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
488
|
+
eventType: "session.regenerate_refused",
|
|
489
|
+
data: { attemptId: cmdMsg.id, reason, source, requestedBy: cmdMsg.requestedBy ?? null },
|
|
490
|
+
}]);
|
|
491
|
+
yield* writeCommandResponse(runtime, { id: cmdMsg.id, cmd: cmdMsg.cmd, error: reason });
|
|
492
|
+
publishStatus(runtime, "idle");
|
|
493
|
+
};
|
|
494
|
+
if (state.regen) {
|
|
495
|
+
yield* refuse("already_pending");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (runtime.options.isSystem) {
|
|
499
|
+
yield* refuse("is_system");
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (!force && state.iteration - state.epochStartIteration < 5) {
|
|
503
|
+
yield* refuse("too_young");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// Cooldown applies to every agent-driven trigger — self (tool) AND
|
|
507
|
+
// parent — so a supervising parent (which a child can prompt-inject)
|
|
508
|
+
// cannot destroy a child's transcript every few turns. Operator and
|
|
509
|
+
// policy triggers bypass the cooldown but still respect min-age.
|
|
510
|
+
const REGEN_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
|
511
|
+
if ((source === "tool" || source === "parent")
|
|
512
|
+
&& state.lastRegenAtMs > 0 && nowMs - state.lastRegenAtMs < REGEN_COOLDOWN_MS) {
|
|
513
|
+
yield* refuse("cooldown");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (source === "tool") {
|
|
517
|
+
// Owner-sender gate: a non-owner-attributed turn must not
|
|
518
|
+
// trigger a self-regen. Positive assertion — refuse whenever an
|
|
519
|
+
// explicitly non-owner sender is present (a collaborator human,
|
|
520
|
+
// OR a child/system-instigated turn whose injection tried to
|
|
521
|
+
// make the agent self-regen). A senderless self-driven turn
|
|
522
|
+
// (the common single-writer case) has no non-owner principal
|
|
523
|
+
// and is allowed. Not gated on the derived multiWriter flag,
|
|
524
|
+
// which never flips for agent/system senders.
|
|
525
|
+
const sender = normalizeMessageSender(cmdMsg.sender);
|
|
526
|
+
if (sender && sender.relation !== "owner") {
|
|
527
|
+
yield* refuse("not_owner");
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else if (source === "parent") {
|
|
532
|
+
if (!cmdMsg.requestedBy || cmdMsg.requestedBy !== runtime.options.parentSessionId) {
|
|
533
|
+
yield* refuse("not_parent");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const handoffRaw = typeof cmdMsg.args?.handoff === "string" ? cmdMsg.args.handoff : undefined;
|
|
538
|
+
state.regen = {
|
|
539
|
+
attemptId: cmdMsg.id,
|
|
540
|
+
stage: "requested",
|
|
541
|
+
requestedAtMs: nowMs,
|
|
542
|
+
trigger: source === "tool" ? "tool" : source === "parent" ? "parent" : source === "policy" ? "policy" : "operator",
|
|
543
|
+
...(cmdMsg.requestedBy ? { requestedBy: cmdMsg.requestedBy } : {}),
|
|
544
|
+
...(handoffRaw ? { handoff: handoffRaw.slice(0, 4_000) } : {}),
|
|
545
|
+
...(typeof cmdMsg.args?.distillerModel === "string" && cmdMsg.args.distillerModel
|
|
546
|
+
? { distillerModel: String(cmdMsg.args.distillerModel) }
|
|
547
|
+
: {}),
|
|
548
|
+
...(typeof cmdMsg.args?.model === "string" && cmdMsg.args.model
|
|
549
|
+
? { model: String(cmdMsg.args.model) }
|
|
550
|
+
: {}),
|
|
551
|
+
};
|
|
552
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
553
|
+
eventType: "session.regenerate_requested",
|
|
554
|
+
data: { attemptId: cmdMsg.id, trigger: state.regen.trigger, requestedBy: cmdMsg.requestedBy ?? null, hasHandoff: Boolean(handoffRaw) },
|
|
555
|
+
}]);
|
|
556
|
+
yield* writeCommandResponse(runtime, {
|
|
557
|
+
id: cmdMsg.id,
|
|
558
|
+
cmd: cmdMsg.cmd,
|
|
559
|
+
result: { ok: true, accepted: true, attemptId: cmdMsg.id, fromEpoch: state.transcriptEpoch, appliesOn: "pipeline" },
|
|
560
|
+
});
|
|
561
|
+
publishStatus(runtime, "running", { regenStage: "requested" });
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
case "cancel_regen": {
|
|
565
|
+
const state = runtime.state;
|
|
566
|
+
if (!state.regen) {
|
|
567
|
+
yield* writeCommandResponse(runtime, { id: cmdMsg.id, cmd: cmdMsg.cmd, error: "no_regen_pending" });
|
|
568
|
+
publishStatus(runtime, "idle");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const cancelled = state.regen.attemptId;
|
|
572
|
+
state.regen = null;
|
|
573
|
+
yield runtime.manager.recordSessionEvent(runtime.input.sessionId, [{
|
|
574
|
+
eventType: "session.regenerate_failed",
|
|
575
|
+
data: { attemptId: cancelled, stage: "cancelled", error: "cancelled by request" },
|
|
576
|
+
}]);
|
|
577
|
+
yield* writeCommandResponse(runtime, { id: cmdMsg.id, cmd: cmdMsg.cmd, result: { ok: true, cancelledAttemptId: cancelled } });
|
|
578
|
+
publishStatus(runtime, "idle");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
case "list_models": {
|
|
582
|
+
publishStatus(runtime, "idle", { cmdProcessing: cmdMsg.id });
|
|
583
|
+
let models;
|
|
584
|
+
try {
|
|
585
|
+
const raw = yield runtime.manager.listModels();
|
|
586
|
+
models = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
const resp = {
|
|
590
|
+
id: cmdMsg.id,
|
|
591
|
+
cmd: cmdMsg.cmd,
|
|
592
|
+
error: err.message || String(err),
|
|
593
|
+
};
|
|
594
|
+
yield* writeCommandResponse(runtime, resp);
|
|
595
|
+
publishStatus(runtime, "idle");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const resp = {
|
|
599
|
+
id: cmdMsg.id,
|
|
600
|
+
cmd: cmdMsg.cmd,
|
|
601
|
+
result: { models, currentModel: runtime.state.config.model },
|
|
602
|
+
};
|
|
603
|
+
yield* writeCommandResponse(runtime, resp);
|
|
604
|
+
publishStatus(runtime, "idle");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
case "get_info": {
|
|
608
|
+
const resp = {
|
|
609
|
+
id: cmdMsg.id,
|
|
610
|
+
cmd: cmdMsg.cmd,
|
|
611
|
+
result: {
|
|
612
|
+
model: runtime.state.config.model || "(default)",
|
|
613
|
+
iteration: runtime.state.iteration,
|
|
614
|
+
sessionId: runtime.input.sessionId,
|
|
615
|
+
affinityKey: runtime.state.affinityKey,
|
|
616
|
+
affinityKeyShort: runtime.state.affinityKey?.slice(0, 8),
|
|
617
|
+
preserveAffinityOnHydrate: runtime.state.preserveAffinityOnHydrate,
|
|
618
|
+
needsHydration: runtime.state.needsHydration,
|
|
619
|
+
blobEnabled: runtime.state.blobEnabled,
|
|
620
|
+
contextUsage: runtime.state.contextUsage,
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
yield* writeCommandResponse(runtime, resp);
|
|
624
|
+
publishStatus(runtime, "idle");
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
case "done":
|
|
628
|
+
case "cancel":
|
|
629
|
+
case "delete": {
|
|
630
|
+
runtime.ctx.traceInfo(`[orch] ${cmdMsg.cmd} command received — beginning graceful ${cmdMsg.cmd}`);
|
|
631
|
+
yield* beginGracefulShutdown(runtime, cmdMsg.cmd, cmdMsg);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
default: {
|
|
635
|
+
const resp = {
|
|
636
|
+
id: cmdMsg.id,
|
|
637
|
+
cmd: cmdMsg.cmd,
|
|
638
|
+
error: `Unknown command: ${cmdMsg.cmd}`,
|
|
639
|
+
};
|
|
640
|
+
yield* writeCommandResponse(runtime, resp);
|
|
641
|
+
publishStatus(runtime, "idle");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function* captureModelSwitchInterruptedTimer(runtime, newModelLabel) {
|
|
647
|
+
const timer = runtime.state.activeTimer;
|
|
648
|
+
if (!timer)
|
|
649
|
+
return;
|
|
650
|
+
const now = yield runtime.ctx.utcNow();
|
|
651
|
+
const notePrefix = `Model switch accepted; continuing immediately on ${newModelLabel}`;
|
|
652
|
+
switch (timer.type) {
|
|
653
|
+
case "wait": {
|
|
654
|
+
const remainingMs = Math.max(0, timer.deadlineMs - now);
|
|
655
|
+
runtime.state.interruptedWaitTimer = {
|
|
656
|
+
remainingSec: Math.max(1, Math.round(remainingMs / 1000)),
|
|
657
|
+
reason: timer.reason,
|
|
658
|
+
shouldRehydrate: timer.shouldRehydrate ?? false,
|
|
659
|
+
...(timer.waitPlan ? { waitPlan: timer.waitPlan } : {}),
|
|
660
|
+
};
|
|
661
|
+
runtime.ctx.traceInfo(`[orch-cmd] ${notePrefix}; will auto-resume interrupted wait (${runtime.state.interruptedWaitTimer.remainingSec}s remain)`);
|
|
662
|
+
runtime.state.activeTimer = null;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
case "cron": {
|
|
666
|
+
const remainingMs = Math.max(0, timer.deadlineMs - now);
|
|
667
|
+
runtime.state.interruptedCronTimer = {
|
|
668
|
+
remainingMs,
|
|
669
|
+
reason: timer.reason,
|
|
670
|
+
originalDurationMs: timer.originalDurationMs,
|
|
671
|
+
...(timer.shouldRehydrate ? { shouldRehydrate: true } : {}),
|
|
672
|
+
};
|
|
673
|
+
runtime.ctx.traceInfo(`[orch-cmd] ${notePrefix}; will auto-resume interrupted cron (${Math.round(remainingMs / 1000)}s remain)`);
|
|
674
|
+
runtime.state.activeTimer = null;
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
case "cron_at":
|
|
678
|
+
case "idle":
|
|
679
|
+
case "agent-poll":
|
|
680
|
+
case "input-grace":
|
|
681
|
+
runtime.ctx.traceInfo(`[orch-cmd] ${notePrefix}; clearing active ${timer.type} timer`);
|
|
682
|
+
runtime.state.activeTimer = null;
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// ─── Session regeneration pipeline (1.0.67, proposal §4) ────────
|
|
687
|
+
//
|
|
688
|
+
// Called from the run loop AFTER drain whenever state.regen is set: one
|
|
689
|
+
// stage per invocation, returning to the loop between stages so a queued
|
|
690
|
+
// cancel_regen / cancel / delete pre-empts a pending regen. Archive and
|
|
691
|
+
// distill are attempt-idempotent activities; any pre-flip failure aborts
|
|
692
|
+
// into the fail-safe (regen cleared, session continues in the old epoch —
|
|
693
|
+
// nothing has changed). The flip is the point of no return: a continue-as-
|
|
694
|
+
// new carrying the fresh epoch, the boundary record, and the flip-mutation
|
|
695
|
+
// table's dispositions (contextUsage zeroed, shared preamble re-armed,
|
|
696
|
+
// pending question and roster carried).
|
|
697
|
+
export function* advanceRegenPipeline(runtime) {
|
|
698
|
+
const { state } = runtime;
|
|
699
|
+
const regen = state.regen;
|
|
700
|
+
if (!regen)
|
|
701
|
+
return false;
|
|
702
|
+
const sessionId = runtime.input.sessionId;
|
|
703
|
+
try {
|
|
704
|
+
if (regen.stage === "requested") {
|
|
705
|
+
publishStatus(runtime, "running", { regenStage: "archiving" });
|
|
706
|
+
const archive = yield runtime.manager.runRegenArchive(sessionId, state.transcriptEpoch, regen.attemptId);
|
|
707
|
+
state.regen = {
|
|
708
|
+
...regen,
|
|
709
|
+
stage: "archived",
|
|
710
|
+
archiveArtifactId: String(archive?.archiveArtifactId ?? ""),
|
|
711
|
+
};
|
|
712
|
+
state.regen.archiveMs = Number(archive?.archiveMs) || 0;
|
|
713
|
+
state.regen.turnsArchived = Number(archive?.turnsArchived) || 0;
|
|
714
|
+
state.regen.compactionsArchived = Number(archive?.compactionsArchived) || 0;
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
if (regen.stage === "archived") {
|
|
718
|
+
publishStatus(runtime, "running", { regenStage: "distilling" });
|
|
719
|
+
const distill = yield runtime.manager.runRegenDistill(sessionId, state.transcriptEpoch, regen.attemptId, {
|
|
720
|
+
...(regen.handoff ? { handoff: regen.handoff } : {}),
|
|
721
|
+
...(state.config.model ? { sessionModel: state.config.model } : {}),
|
|
722
|
+
...(regen.distillerModel ? { distillerModel: regen.distillerModel } : {}),
|
|
723
|
+
...(regen.archiveArtifactId ? { archiveArtifactId: regen.archiveArtifactId } : {}),
|
|
724
|
+
});
|
|
725
|
+
const bootstrap = String(distill?.bootstrap ?? "");
|
|
726
|
+
if (!bootstrap)
|
|
727
|
+
throw new Error("distill produced no bootstrap");
|
|
728
|
+
state.regen = {
|
|
729
|
+
...regen,
|
|
730
|
+
stage: "distilled",
|
|
731
|
+
packageArtifactId: String(distill?.packageArtifactId ?? ""),
|
|
732
|
+
bootstrap,
|
|
733
|
+
};
|
|
734
|
+
state.regen.distillMs = Number(distill?.distillMs) || 0;
|
|
735
|
+
state.regen.distillerModel = String(distill?.distillerModel ?? "");
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
// stage "distilled" → FLIP. COMMIT POINT: the distill result is in
|
|
739
|
+
// history; from here replay roll-forwards through the CAN.
|
|
740
|
+
const nowMs = yield runtime.ctx.utcNow();
|
|
741
|
+
publishStatus(runtime, "running", { regenStage: "flipping" });
|
|
742
|
+
const fromEpoch = state.transcriptEpoch;
|
|
743
|
+
const toEpoch = fromEpoch + 1;
|
|
744
|
+
const r = regen;
|
|
745
|
+
// Clear the armed idle/affinity/cron timer (stashing wait/cron as
|
|
746
|
+
// interrupted so they re-arm after the grounding turn) — exactly as
|
|
747
|
+
// set_model does before its bootstrap CAN. Without this the reborn
|
|
748
|
+
// execution restores the timer and drain parks on it BEFORE decide
|
|
749
|
+
// dispatches the grounding bootstrap, delaying the rebirth for up to
|
|
750
|
+
// the hold window.
|
|
751
|
+
yield* captureModelSwitchInterruptedTimer(runtime, "regenerated context");
|
|
752
|
+
// Optional model rebind: the reborn session's grounding turn runs on
|
|
753
|
+
// config.model, so an operator-supplied replacement (e.g. escaping a
|
|
754
|
+
// removed model) must be applied to the carried config before the CAN.
|
|
755
|
+
if (r.model && typeof r.model === "string") {
|
|
756
|
+
state.config = { ...state.config, model: r.model };
|
|
757
|
+
yield runtime.manager.updateSessionModel(sessionId, r.model);
|
|
758
|
+
}
|
|
759
|
+
// Drop runtime notices that describe the dead transcript before the
|
|
760
|
+
// carry-list is built.
|
|
761
|
+
state.runtimeModelNotice = undefined;
|
|
762
|
+
const canInput = {
|
|
763
|
+
...continueInputWithPrompt(runtime, regen.bootstrap ?? "", { bootstrapPrompt: true }),
|
|
764
|
+
// Grounding is prompt-level in M1 (FIRST ACTIONS in the rendered
|
|
765
|
+
// bootstrap): a hard requiredTool would fail the grounding turn on
|
|
766
|
+
// deployments without fact tools. Enforce once fact tools are
|
|
767
|
+
// guaranteed present.
|
|
768
|
+
transcriptEpoch: toEpoch,
|
|
769
|
+
epochStartPending: true,
|
|
770
|
+
epochStartIteration: state.iteration,
|
|
771
|
+
lastRegenAtMs: nowMs,
|
|
772
|
+
regen: undefined,
|
|
773
|
+
contextUsage: undefined,
|
|
774
|
+
sharedPreambleSent: false,
|
|
775
|
+
pendingEpochCommit: {
|
|
776
|
+
fromEpoch,
|
|
777
|
+
toEpoch,
|
|
778
|
+
attemptId: regen.attemptId,
|
|
779
|
+
trigger: regen.trigger,
|
|
780
|
+
requestedAtMs: regen.requestedAtMs,
|
|
781
|
+
...(regen.archiveArtifactId ? { archiveArtifactId: regen.archiveArtifactId } : {}),
|
|
782
|
+
...(regen.packageArtifactId ? { packageArtifactId: regen.packageArtifactId } : {}),
|
|
783
|
+
...(r.turnsArchived ? { turnsArchived: r.turnsArchived } : {}),
|
|
784
|
+
...(r.compactionsArchived ? { compactionsArchived: r.compactionsArchived } : {}),
|
|
785
|
+
...(r.archiveMs ? { archiveMs: r.archiveMs } : {}),
|
|
786
|
+
...(r.distillMs ? { distillMs: r.distillMs } : {}),
|
|
787
|
+
},
|
|
788
|
+
};
|
|
789
|
+
yield* versionedContinueAsNew(runtime, canInput);
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
// FAIL-SAFE (pre-flip only): clear the pipeline so a later attempt is
|
|
794
|
+
// not refused as pending; the session continues in the old epoch.
|
|
795
|
+
const failedStage = state.regen?.stage ?? "requested";
|
|
796
|
+
const attemptId = state.regen?.attemptId ?? "unknown";
|
|
797
|
+
state.regen = null;
|
|
798
|
+
runtime.ctx.traceInfo(`[orch] regen attempt ${attemptId} failed at ${failedStage}: ${err?.message ?? err}`);
|
|
799
|
+
yield runtime.manager.recordSessionEvent(sessionId, [{
|
|
800
|
+
eventType: "session.regenerate_failed",
|
|
801
|
+
data: { attemptId, stage: failedStage, error: String(err?.message ?? err).slice(0, 500) },
|
|
802
|
+
}]);
|
|
803
|
+
publishStatus(runtime, "idle");
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
//# sourceMappingURL=lifecycle.js.map
|