@parall/agent-core 1.29.3 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatch-adapter.d.ts +1 -0
- package/dist/dispatch-adapter.d.ts.map +1 -1
- package/dist/gateway-base.d.ts +3 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +86 -53
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/provider-config.d.ts +11 -0
- package/dist/provider-config.d.ts.map +1 -0
- package/dist/provider-config.js +30 -0
- package/package.json +2 -2
- package/src/dispatch-adapter.ts +1 -0
- package/src/gateway-base.ts +89 -53
- package/src/index.ts +2 -1
- package/src/logger.ts +5 -0
- package/src/provider-config.ts +39 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-adapter.d.ts","sourceRoot":"","sources":["../src/dispatch-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch-adapter.d.ts","sourceRoot":"","sources":["../src/dispatch-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/gateway-base.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type ParallGatewayOptions = {
|
|
|
35
35
|
runtimeLaneKey: string;
|
|
36
36
|
}) => Promise<void> | void;
|
|
37
37
|
onBeforeDisconnect?: () => Promise<void> | void;
|
|
38
|
+
onNewSession?: (previousSessionId: string) => Promise<void> | void;
|
|
39
|
+
onSessionStale?: (sessionKey: string) => Promise<void> | void;
|
|
38
40
|
};
|
|
39
41
|
export declare function parseShutdownDeadlineMs(raw: string | undefined): number | undefined;
|
|
40
42
|
export declare class ParallAgentGateway {
|
|
@@ -56,6 +58,7 @@ export declare class ParallAgentGateway {
|
|
|
56
58
|
private shuttingDown;
|
|
57
59
|
private inFlightDispatches;
|
|
58
60
|
private drainResolvers;
|
|
61
|
+
private pendingRestartNotification;
|
|
59
62
|
private readonly DISPATCHED_MESSAGES_CAP;
|
|
60
63
|
private readonly COLD_START_WINDOW_MS;
|
|
61
64
|
private readonly SHUTDOWN_DEADLINE_MS;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EACV,qBAAqB,
|
|
1
|
+
{"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
|
package/dist/gateway-base.js
CHANGED
|
@@ -5,6 +5,7 @@ import { MENTION_ALL_USER_ID } from "@parall/sdk";
|
|
|
5
5
|
import { buildEventBody, buildEventBodyForForkResult, buildForkResultPrefix, buildForkScopePrefix } from "./event-format.js";
|
|
6
6
|
import { routeTrigger } from "./routing.js";
|
|
7
7
|
import { clearDispatchMessageId, clearDispatchNoReply, clearSessionMessageId, setDispatchMessageId, setDispatchNoReply, setSessionChatId, setSessionMessageId, } from "./session-state.js";
|
|
8
|
+
const LIVE_SESSION_STATUSES = new Set(["open", "active", "idle"]);
|
|
8
9
|
// Parse PRLL_SHUTDOWN_DEADLINE_MS (or any string env value) into a positive
|
|
9
10
|
// integer milliseconds value, or undefined if unset/invalid. Runtimes pass
|
|
10
11
|
// the result into ParallGatewayOptions.shutdownDeadlineMs; leaving it
|
|
@@ -78,6 +79,7 @@ export class ParallAgentGateway {
|
|
|
78
79
|
shuttingDown = false;
|
|
79
80
|
inFlightDispatches = 0;
|
|
80
81
|
drainResolvers = [];
|
|
82
|
+
pendingRestartNotification = null;
|
|
81
83
|
DISPATCHED_MESSAGES_CAP = 5000;
|
|
82
84
|
COLD_START_WINDOW_MS;
|
|
83
85
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
@@ -92,7 +94,7 @@ export class ParallAgentGateway {
|
|
|
92
94
|
async run(abortSignal) {
|
|
93
95
|
const { ws, log } = this.opts;
|
|
94
96
|
ws.onStateChange((state) => {
|
|
95
|
-
log?.info(`
|
|
97
|
+
log?.info(`connection state → ${state}`);
|
|
96
98
|
});
|
|
97
99
|
ws.on("hello", async (data) => {
|
|
98
100
|
await this.handleHello(data);
|
|
@@ -126,17 +128,32 @@ export class ParallAgentGateway {
|
|
|
126
128
|
await this.handleMessage(data);
|
|
127
129
|
});
|
|
128
130
|
ws.on("agent_config.update", async (data) => {
|
|
129
|
-
this.opts.log?.info(`
|
|
131
|
+
this.opts.log?.info(`config update notification (version=${data.version})`);
|
|
130
132
|
try {
|
|
131
133
|
await this.opts.onConfigUpdate?.(data);
|
|
132
134
|
}
|
|
133
135
|
catch (err) {
|
|
134
|
-
this.opts.log?.warn(`
|
|
136
|
+
this.opts.log?.warn(`config update failed: ${String(err)}`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
ws.on("agent.new_session", async (data) => {
|
|
140
|
+
const prevId = data.previous_session_id ?? "";
|
|
141
|
+
this.opts.log?.info(`new session signal received (previous=${prevId})`);
|
|
142
|
+
this.sessionBindings.clear();
|
|
143
|
+
if (prevId) {
|
|
144
|
+
this.pendingRestartNotification =
|
|
145
|
+
`[Harness Notification] This is a fresh session. Your previous session (${prevId}) was ended by the user and you have been restarted.`;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await this.opts.onNewSession?.(prevId);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
this.opts.log?.warn(`onNewSession callback failed: ${String(err)}`);
|
|
135
152
|
}
|
|
136
153
|
});
|
|
137
154
|
ws.on("recovery.overflow", () => {
|
|
138
|
-
this.opts.log?.warn(`
|
|
139
|
-
this.catchUpFromDispatch().catch((err) => this.opts.log?.warn(`
|
|
155
|
+
this.opts.log?.warn(`recovery.overflow — triggering full catch-up`);
|
|
156
|
+
this.catchUpFromDispatch().catch((err) => this.opts.log?.warn(`overflow catch-up failed: ${String(err)}`));
|
|
140
157
|
});
|
|
141
158
|
ws.on("task.assigned", async (data) => {
|
|
142
159
|
if (data.assignee_id !== this.opts.agentUserId)
|
|
@@ -150,7 +167,7 @@ export class ParallAgentGateway {
|
|
|
150
167
|
}
|
|
151
168
|
}
|
|
152
169
|
catch (err) {
|
|
153
|
-
this.opts.log?.error(`
|
|
170
|
+
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
154
171
|
}
|
|
155
172
|
});
|
|
156
173
|
ws.on("dispatch.new", async (data) => {
|
|
@@ -164,7 +181,7 @@ export class ParallAgentGateway {
|
|
|
164
181
|
}
|
|
165
182
|
}
|
|
166
183
|
catch (err) {
|
|
167
|
-
this.opts.log?.error(`
|
|
184
|
+
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
187
|
else if (data.event_type === "task_update") {
|
|
@@ -177,7 +194,7 @@ export class ParallAgentGateway {
|
|
|
177
194
|
}
|
|
178
195
|
}
|
|
179
196
|
catch (err) {
|
|
180
|
-
this.opts.log?.error(`
|
|
197
|
+
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
181
198
|
}
|
|
182
199
|
}
|
|
183
200
|
else if (data.event_type === "schedule.fire") {
|
|
@@ -190,7 +207,7 @@ export class ParallAgentGateway {
|
|
|
190
207
|
}
|
|
191
208
|
}
|
|
192
209
|
catch (err) {
|
|
193
|
-
this.opts.log?.error(`
|
|
210
|
+
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
194
211
|
}
|
|
195
212
|
}
|
|
196
213
|
else if (data.event_type === "approval_decided") {
|
|
@@ -203,7 +220,7 @@ export class ParallAgentGateway {
|
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
catch (err) {
|
|
206
|
-
this.opts.log?.error(`
|
|
223
|
+
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
207
224
|
}
|
|
208
225
|
}
|
|
209
226
|
else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
@@ -214,10 +231,10 @@ export class ParallAgentGateway {
|
|
|
214
231
|
// (message.new, task.assigned) above and would otherwise spam
|
|
215
232
|
// info-level logs for every inbound chat message / task
|
|
216
233
|
// assignment on a busy agent.
|
|
217
|
-
this.opts.log?.info(`
|
|
234
|
+
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) — no-op`);
|
|
218
235
|
}
|
|
219
236
|
});
|
|
220
|
-
this.opts.log?.info(`
|
|
237
|
+
this.opts.log?.info(`connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
|
|
221
238
|
await ws.connect();
|
|
222
239
|
return new Promise((resolve) => {
|
|
223
240
|
abortSignal.addEventListener("abort", async () => {
|
|
@@ -341,7 +358,7 @@ export class ParallAgentGateway {
|
|
|
341
358
|
});
|
|
342
359
|
}
|
|
343
360
|
catch (err) {
|
|
344
|
-
this.opts.log?.warn(`
|
|
361
|
+
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
345
362
|
}
|
|
346
363
|
}
|
|
347
364
|
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
@@ -421,13 +438,13 @@ export class ParallAgentGateway {
|
|
|
421
438
|
target_type: target.target_type,
|
|
422
439
|
target_id: target.target_id,
|
|
423
440
|
content: { text: runtimeEvent.message, suppressed: false },
|
|
424
|
-
projection:
|
|
441
|
+
projection: false,
|
|
425
442
|
});
|
|
426
443
|
break;
|
|
427
444
|
}
|
|
428
445
|
}
|
|
429
446
|
catch (err) {
|
|
430
|
-
this.opts.log?.warn(`
|
|
447
|
+
this.opts.log?.warn(`failed to create ${runtimeEvent.type} step: ${String(err)}`);
|
|
431
448
|
}
|
|
432
449
|
}
|
|
433
450
|
writeContextFile(filePath, ctx) {
|
|
@@ -436,7 +453,7 @@ export class ParallAgentGateway {
|
|
|
436
453
|
fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
437
454
|
}
|
|
438
455
|
catch (err) {
|
|
439
|
-
this.opts.log?.warn(`
|
|
456
|
+
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
440
457
|
}
|
|
441
458
|
}
|
|
442
459
|
updateContextFileStepId(filePath, stepId) {
|
|
@@ -447,7 +464,7 @@ export class ParallAgentGateway {
|
|
|
447
464
|
fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
448
465
|
}
|
|
449
466
|
catch (err) {
|
|
450
|
-
this.opts.log?.warn(`
|
|
467
|
+
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
451
468
|
}
|
|
452
469
|
}
|
|
453
470
|
updateContextFileSessionId(filePath, sessionId) {
|
|
@@ -458,7 +475,7 @@ export class ParallAgentGateway {
|
|
|
458
475
|
fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
459
476
|
}
|
|
460
477
|
catch (err) {
|
|
461
|
-
this.opts.log?.warn(`
|
|
478
|
+
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
462
479
|
}
|
|
463
480
|
}
|
|
464
481
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
@@ -468,7 +485,7 @@ export class ParallAgentGateway {
|
|
|
468
485
|
fs.writeFileSync(filePath, stepId, "utf8");
|
|
469
486
|
}
|
|
470
487
|
catch (err) {
|
|
471
|
-
this.opts.log?.warn(`
|
|
488
|
+
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
472
489
|
}
|
|
473
490
|
}
|
|
474
491
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
@@ -508,6 +525,18 @@ export class ParallAgentGateway {
|
|
|
508
525
|
parent_session_id: parentSessionId,
|
|
509
526
|
runtime_ref: Object.keys(runtimeRef).length > 0 ? runtimeRef : undefined,
|
|
510
527
|
});
|
|
528
|
+
if (!LIVE_SESSION_STATUSES.has(session.status)) {
|
|
529
|
+
this.opts.log?.warn?.(`createAgentSession returned terminal session ${session.id} (${session.status}), resetting runtime for ${sessionKey}`);
|
|
530
|
+
this.sessionBindings.delete(sessionKey);
|
|
531
|
+
try {
|
|
532
|
+
await this.opts.onSessionStale?.(sessionKey);
|
|
533
|
+
}
|
|
534
|
+
catch (e) {
|
|
535
|
+
this.opts.log?.warn?.(`onSessionStale failed: ${e}`);
|
|
536
|
+
}
|
|
537
|
+
this.opts.log?.info?.(`stale session self-heal complete for ${sessionKey} — next dispatch will create a fresh session`);
|
|
538
|
+
throw new Error(`Agent session ${session.id} is terminal (${session.status})`);
|
|
539
|
+
}
|
|
511
540
|
const binding = {
|
|
512
541
|
sessionKey,
|
|
513
542
|
agentSessionId: session.id,
|
|
@@ -534,9 +563,13 @@ export class ParallAgentGateway {
|
|
|
534
563
|
// In-flight dispatches that started before the flag flipped keep running
|
|
535
564
|
// and are awaited by shutdown() up to SHUTDOWN_DEADLINE_MS.
|
|
536
565
|
if (this.shuttingDown) {
|
|
537
|
-
this.opts.log?.info(`
|
|
566
|
+
this.opts.log?.info(`skipping dispatch for ${event.messageId} (shutting down) — leaving unacked for catch-up on replacement pod`);
|
|
538
567
|
return false;
|
|
539
568
|
}
|
|
569
|
+
if (this.pendingRestartNotification) {
|
|
570
|
+
bodyForAgent = this.pendingRestartNotification + "\n\n---\n\n" + bodyForAgent;
|
|
571
|
+
this.pendingRestartNotification = null;
|
|
572
|
+
}
|
|
540
573
|
setSessionChatId(sessionKey, event.targetId);
|
|
541
574
|
setSessionMessageId(sessionKey, event.messageId);
|
|
542
575
|
setDispatchMessageId(sessionKey, event.messageId);
|
|
@@ -685,7 +718,7 @@ export class ParallAgentGateway {
|
|
|
685
718
|
}
|
|
686
719
|
}
|
|
687
720
|
catch (err) {
|
|
688
|
-
this.opts.log?.error(`
|
|
721
|
+
this.opts.log?.error(`fork dispatch failed for ${last.messageId}: ${String(err)}`);
|
|
689
722
|
for (const item of items) {
|
|
690
723
|
item.resolve(false);
|
|
691
724
|
}
|
|
@@ -705,7 +738,7 @@ export class ParallAgentGateway {
|
|
|
705
738
|
historyPath = this.opts.dispatchAdapter.getSessionHistoryPath?.(fork.fork.sessionKey);
|
|
706
739
|
}
|
|
707
740
|
catch (err) {
|
|
708
|
-
this.opts.log?.warn?.(`
|
|
741
|
+
this.opts.log?.warn?.(`failed to resolve fork history path: ${String(err)}`);
|
|
709
742
|
}
|
|
710
743
|
this.dispatchState.pendingForkResults.push({
|
|
711
744
|
forkSessionKey: fork.fork.sessionKey,
|
|
@@ -761,7 +794,7 @@ export class ParallAgentGateway {
|
|
|
761
794
|
// pendingForkResults intact so dispatch catch-up on the replacement
|
|
762
795
|
// pod is the single source of truth for replay.
|
|
763
796
|
if (this.shuttingDown) {
|
|
764
|
-
this.opts.log?.info(`
|
|
797
|
+
this.opts.log?.info(`drainMainBuffer halted (shutting down) — ${this.dispatchState.mainBuffer.length} buffered, ${this.dispatchState.pendingForkResults.length} pending fork results left for catch-up`);
|
|
765
798
|
break;
|
|
766
799
|
}
|
|
767
800
|
const targetId = this.dispatchState.mainBuffer[0].targetId;
|
|
@@ -842,7 +875,7 @@ export class ParallAgentGateway {
|
|
|
842
875
|
if (this.dispatchState.mainCurrentTargetId === event.targetId &&
|
|
843
876
|
await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
844
877
|
this.startInjectedTyping(event);
|
|
845
|
-
this.opts.log?.info(`
|
|
878
|
+
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
846
879
|
}
|
|
847
880
|
// If the main dispatch cycle ended while we awaited the steer RPC,
|
|
848
881
|
// our event is buffered but no drain is in flight. Re-enter the
|
|
@@ -874,7 +907,7 @@ export class ParallAgentGateway {
|
|
|
874
907
|
preDispatchBranchPoint: this.dispatchState.mainPreDispatchBranchPoint,
|
|
875
908
|
});
|
|
876
909
|
if (!fork) {
|
|
877
|
-
this.opts.log?.warn(`
|
|
910
|
+
this.opts.log?.warn(`fork failed, buffering event for main session`);
|
|
878
911
|
this.dispatchState.mainBuffer.push(event);
|
|
879
912
|
return false;
|
|
880
913
|
}
|
|
@@ -890,7 +923,7 @@ export class ParallAgentGateway {
|
|
|
890
923
|
activeFork.queue.push({ event, resolve });
|
|
891
924
|
});
|
|
892
925
|
this.runForkDrainLoop(activeFork).catch((err) => {
|
|
893
|
-
this.opts.log?.error(`
|
|
926
|
+
this.opts.log?.error(`fork drain loop error: ${String(err)}`);
|
|
894
927
|
});
|
|
895
928
|
return firstEventPromise;
|
|
896
929
|
}
|
|
@@ -911,7 +944,7 @@ export class ParallAgentGateway {
|
|
|
911
944
|
return chatInfo;
|
|
912
945
|
}
|
|
913
946
|
catch (err) {
|
|
914
|
-
this.opts.log?.warn(`
|
|
947
|
+
this.opts.log?.warn(`failed to resolve chat ${chatId}: ${String(err)}`);
|
|
915
948
|
return null;
|
|
916
949
|
}
|
|
917
950
|
}
|
|
@@ -985,7 +1018,7 @@ export class ParallAgentGateway {
|
|
|
985
1018
|
}
|
|
986
1019
|
}
|
|
987
1020
|
catch (err) {
|
|
988
|
-
this.opts.log?.error(`
|
|
1021
|
+
this.opts.log?.error(`event dispatch failed for ${data.id}: ${String(err)}`);
|
|
989
1022
|
this.dispatchedMessages.delete(data.id);
|
|
990
1023
|
}
|
|
991
1024
|
}
|
|
@@ -994,11 +1027,11 @@ export class ParallAgentGateway {
|
|
|
994
1027
|
return false; // drain window — let server requeue via catch-up
|
|
995
1028
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
996
1029
|
if (this.dispatchedTasks.has(dedupeKey)) {
|
|
997
|
-
this.opts.log?.info(`
|
|
1030
|
+
this.opts.log?.info(`skipping already-dispatched task ${task.identifier ?? task.id}`);
|
|
998
1031
|
return false;
|
|
999
1032
|
}
|
|
1000
1033
|
this.dispatchedTasks.add(dedupeKey);
|
|
1001
|
-
this.opts.log?.info(`
|
|
1034
|
+
this.opts.log?.info(`task assigned: ${task.identifier ?? task.id} "${task.title}"`);
|
|
1002
1035
|
const parts = [`Title: ${task.title}`];
|
|
1003
1036
|
parts.push(`Status: ${task.status}`, `Priority: ${task.priority}`);
|
|
1004
1037
|
if (task.project_id)
|
|
@@ -1040,7 +1073,7 @@ export class ParallAgentGateway {
|
|
|
1040
1073
|
const isAssignee = task.assignee_id === this.opts.agentUserId;
|
|
1041
1074
|
const isCreatorUpdate = opts.allowCreator === true && task.creator_id === this.opts.agentUserId;
|
|
1042
1075
|
if (!isAssignee && !isCreatorUpdate) {
|
|
1043
|
-
this.opts.log?.info(`
|
|
1076
|
+
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} — assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
1044
1077
|
return true;
|
|
1045
1078
|
}
|
|
1046
1079
|
return this.handleTaskAssignment(task, ackSourceId);
|
|
@@ -1060,7 +1093,7 @@ export class ParallAgentGateway {
|
|
|
1060
1093
|
const status = err?.status;
|
|
1061
1094
|
if (status === 404) {
|
|
1062
1095
|
// Comment deleted — ack stale dispatch so it doesn't pile up.
|
|
1063
|
-
this.opts.log?.info(`
|
|
1096
|
+
this.opts.log?.info(`skipping deleted comment ${commentId}, acking stale dispatch`);
|
|
1064
1097
|
this.dispatchedTasks.delete(dedupeKey);
|
|
1065
1098
|
return true; // caller will ack
|
|
1066
1099
|
}
|
|
@@ -1072,7 +1105,7 @@ export class ParallAgentGateway {
|
|
|
1072
1105
|
return true; // null response = gone, ack stale dispatch
|
|
1073
1106
|
}
|
|
1074
1107
|
if (comment.hints?.no_reply) {
|
|
1075
|
-
this.opts.log?.info(`
|
|
1108
|
+
this.opts.log?.info(`skipping no_reply task comment ${commentId}, acking stale dispatch`);
|
|
1076
1109
|
this.dispatchedTasks.delete(dedupeKey);
|
|
1077
1110
|
return true;
|
|
1078
1111
|
}
|
|
@@ -1082,7 +1115,7 @@ export class ParallAgentGateway {
|
|
|
1082
1115
|
}
|
|
1083
1116
|
catch { /* task context is optional */ }
|
|
1084
1117
|
const taskLabel = task ? `${task.identifier ?? task.id} "${task.title}"` : taskId;
|
|
1085
|
-
this.opts.log?.info(`
|
|
1118
|
+
this.opts.log?.info(`task comment on ${taskLabel} by ${actorId ?? "unknown"}`);
|
|
1086
1119
|
const parts = [];
|
|
1087
1120
|
if (task) {
|
|
1088
1121
|
parts.push(`Task: ${task.title} (prll://${task.id})`);
|
|
@@ -1141,10 +1174,10 @@ export class ParallAgentGateway {
|
|
|
1141
1174
|
// already-delivered run — this is expected but operationally
|
|
1142
1175
|
// visible, so warn (not info) keeps it findable during incident
|
|
1143
1176
|
// triage. See docs/primitives/schedule.md § Snapshot 不变性.
|
|
1144
|
-
this.opts.log?.warn(`
|
|
1177
|
+
this.opts.log?.warn(`schedule run ${runId} not accessible (404), acking stale dispatch`);
|
|
1145
1178
|
return true;
|
|
1146
1179
|
}
|
|
1147
|
-
this.opts.log?.warn(`
|
|
1180
|
+
this.opts.log?.warn(`schedule run fetch failed for ${runId}, leaving pending: ${String(err)}`);
|
|
1148
1181
|
return false;
|
|
1149
1182
|
}
|
|
1150
1183
|
if (!run)
|
|
@@ -1158,7 +1191,7 @@ export class ParallAgentGateway {
|
|
|
1158
1191
|
if (this.dispatchedTasks.has(dedupeKey))
|
|
1159
1192
|
return false;
|
|
1160
1193
|
this.dispatchedTasks.add(dedupeKey);
|
|
1161
|
-
this.opts.log?.info(`
|
|
1194
|
+
this.opts.log?.info(`schedule fired: ${run.id} (schedule ${run.schedule_id})`);
|
|
1162
1195
|
const event = {
|
|
1163
1196
|
type: "schedule",
|
|
1164
1197
|
// Route by schedule_id (not attached chat_id) so concurrent fires of
|
|
@@ -1197,10 +1230,10 @@ export class ParallAgentGateway {
|
|
|
1197
1230
|
catch (err) {
|
|
1198
1231
|
const status = err?.status;
|
|
1199
1232
|
if (status === 404 || status === 403) {
|
|
1200
|
-
this.opts.log?.warn(`
|
|
1233
|
+
this.opts.log?.warn(`approval ${approvalId} not accessible (${status}), acking stale dispatch`);
|
|
1201
1234
|
return true;
|
|
1202
1235
|
}
|
|
1203
|
-
this.opts.log?.warn(`
|
|
1236
|
+
this.opts.log?.warn(`approval fetch failed for ${approvalId}, leaving pending: ${String(err)}`);
|
|
1204
1237
|
return false;
|
|
1205
1238
|
}
|
|
1206
1239
|
if (!approval)
|
|
@@ -1211,7 +1244,7 @@ export class ParallAgentGateway {
|
|
|
1211
1244
|
if (this.dispatchedTasks.has(dedupeKey))
|
|
1212
1245
|
return false;
|
|
1213
1246
|
this.dispatchedTasks.add(dedupeKey);
|
|
1214
|
-
this.opts.log?.info(`
|
|
1247
|
+
this.opts.log?.info(`approval decided: ${approval.id} (${approval.status})`);
|
|
1215
1248
|
const statusLabel = approval.status === "approved" ? "Approved" : "Rejected";
|
|
1216
1249
|
const execInfo = approval.execution_status ? ` | execution: ${approval.execution_status}` : "";
|
|
1217
1250
|
const body = `${statusLabel}: ${approval.title}${execInfo}`;
|
|
@@ -1264,7 +1297,7 @@ export class ParallAgentGateway {
|
|
|
1264
1297
|
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id);
|
|
1265
1298
|
}
|
|
1266
1299
|
catch (err) {
|
|
1267
|
-
this.opts.log?.warn(`
|
|
1300
|
+
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1268
1301
|
continue;
|
|
1269
1302
|
}
|
|
1270
1303
|
}
|
|
@@ -1273,7 +1306,7 @@ export class ParallAgentGateway {
|
|
|
1273
1306
|
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id, { allowCreator: true });
|
|
1274
1307
|
}
|
|
1275
1308
|
catch (err) {
|
|
1276
|
-
this.opts.log?.warn(`
|
|
1309
|
+
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1277
1310
|
continue;
|
|
1278
1311
|
}
|
|
1279
1312
|
}
|
|
@@ -1301,7 +1334,7 @@ export class ParallAgentGateway {
|
|
|
1301
1334
|
}
|
|
1302
1335
|
else {
|
|
1303
1336
|
msgFetchFailed = true;
|
|
1304
|
-
this.opts.log?.warn(`
|
|
1337
|
+
this.opts.log?.warn(`catch-up message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
1305
1338
|
}
|
|
1306
1339
|
}
|
|
1307
1340
|
if (msgFetchFailed) {
|
|
@@ -1330,7 +1363,7 @@ export class ParallAgentGateway {
|
|
|
1330
1363
|
}
|
|
1331
1364
|
}
|
|
1332
1365
|
catch (err) {
|
|
1333
|
-
this.opts.log?.warn(`
|
|
1366
|
+
this.opts.log?.warn(`catch-up dispatch ${item.id} (${item.event_type}) failed: ${String(err)}`);
|
|
1334
1367
|
}
|
|
1335
1368
|
}
|
|
1336
1369
|
// Stop paginating once shutdown begins — the inner loop already broke,
|
|
@@ -1338,7 +1371,7 @@ export class ParallAgentGateway {
|
|
|
1338
1371
|
cursor = !this.shuttingDown && page.has_more ? page.next_cursor : undefined;
|
|
1339
1372
|
} while (cursor);
|
|
1340
1373
|
if (processed > 0 || skippedOld > 0) {
|
|
1341
|
-
this.opts.log?.info(`
|
|
1374
|
+
this.opts.log?.info(`dispatch catch-up: processed ${processed}, skipped ${skippedOld} old item(s)`);
|
|
1342
1375
|
}
|
|
1343
1376
|
}
|
|
1344
1377
|
async handleHello(data) {
|
|
@@ -1347,7 +1380,7 @@ export class ParallAgentGateway {
|
|
|
1347
1380
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
1348
1381
|
try {
|
|
1349
1382
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
1350
|
-
log?.info(`
|
|
1383
|
+
log?.info(`WebSocket connected, ${count} chats cached`);
|
|
1351
1384
|
await this.opts.onSessionReady?.({
|
|
1352
1385
|
activeSessionId: this.activeSessionId,
|
|
1353
1386
|
ws: this.opts.ws,
|
|
@@ -1361,7 +1394,7 @@ export class ParallAgentGateway {
|
|
|
1361
1394
|
const expectedMs = intervalSec * 1000;
|
|
1362
1395
|
const drift = now - this.lastHeartbeatAt - expectedMs;
|
|
1363
1396
|
if (drift > 15000) {
|
|
1364
|
-
log?.warn(`
|
|
1397
|
+
log?.warn(`heartbeat drift ${drift}ms — event loop may be blocked`);
|
|
1365
1398
|
}
|
|
1366
1399
|
this.lastHeartbeatAt = now;
|
|
1367
1400
|
if (this.opts.ws.state !== "connected")
|
|
@@ -1377,11 +1410,11 @@ export class ParallAgentGateway {
|
|
|
1377
1410
|
const isFirstHello = !this.hadSuccessfulHello;
|
|
1378
1411
|
this.hadSuccessfulHello = true;
|
|
1379
1412
|
this.catchUpFromDispatch(isFirstHello).catch((err) => {
|
|
1380
|
-
log?.warn(`
|
|
1413
|
+
log?.warn(`dispatch catch-up failed: ${String(err)}`);
|
|
1381
1414
|
});
|
|
1382
1415
|
}
|
|
1383
1416
|
catch (err) {
|
|
1384
|
-
log?.error(`
|
|
1417
|
+
log?.error(`failed to fetch chats: ${String(err)}`);
|
|
1385
1418
|
}
|
|
1386
1419
|
}
|
|
1387
1420
|
// Resolves when in-flight dispatches hit 0 or the deadline elapses.
|
|
@@ -1414,13 +1447,13 @@ export class ParallAgentGateway {
|
|
|
1414
1447
|
// the process exit — that's acceptable because K8s will retry the event
|
|
1415
1448
|
// via dispatch catch-up after the new pod starts.
|
|
1416
1449
|
if (this.inFlightDispatches > 0) {
|
|
1417
|
-
this.opts.log?.info(`
|
|
1450
|
+
this.opts.log?.info(`draining ${this.inFlightDispatches} in-flight dispatch(es), deadline ${this.SHUTDOWN_DEADLINE_MS}ms`);
|
|
1418
1451
|
await this.waitForDrain(this.SHUTDOWN_DEADLINE_MS);
|
|
1419
1452
|
if (this.inFlightDispatches > 0) {
|
|
1420
|
-
this.opts.log?.warn(`
|
|
1453
|
+
this.opts.log?.warn(`drain deadline hit; ${this.inFlightDispatches} dispatch(es) still running — they will be killed by process exit`);
|
|
1421
1454
|
}
|
|
1422
1455
|
else {
|
|
1423
|
-
this.opts.log?.info(`
|
|
1456
|
+
this.opts.log?.info(`drain complete`);
|
|
1424
1457
|
}
|
|
1425
1458
|
}
|
|
1426
1459
|
if (this.heartbeatTimer)
|
|
@@ -1431,6 +1464,6 @@ export class ParallAgentGateway {
|
|
|
1431
1464
|
this.activeDispatches.clear();
|
|
1432
1465
|
await this.opts.onBeforeDisconnect?.();
|
|
1433
1466
|
this.opts.ws.disconnect();
|
|
1434
|
-
this.opts.log?.info(`
|
|
1467
|
+
this.opts.log?.info(`disconnected`);
|
|
1435
1468
|
}
|
|
1436
1469
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./provider-config.js";
|
|
1
2
|
export * from "./types.js";
|
|
2
3
|
export * from "./session-state.js";
|
|
3
4
|
export * from "./routing.js";
|
|
@@ -5,7 +6,7 @@ export * from "./event-format.js";
|
|
|
5
6
|
export * from "./prompt-fragments.js";
|
|
6
7
|
export * from "./bridge-workspace.js";
|
|
7
8
|
export * from "./dispatch-adapter.js";
|
|
8
|
-
export { createLogger } from "./logger.js";
|
|
9
|
+
export { createLogger, childLogger } from "./logger.js";
|
|
9
10
|
export * from "./gateway-base.js";
|
|
10
11
|
export * from "./platform-config.js";
|
|
11
12
|
export { writeSkillFiles, buildSkillReferences, SKILLS } from "./skills/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./provider-config.js";
|
|
1
2
|
export * from "./types.js";
|
|
2
3
|
export * from "./session-state.js";
|
|
3
4
|
export * from "./routing.js";
|
|
@@ -5,7 +6,7 @@ export * from "./event-format.js";
|
|
|
5
6
|
export * from "./prompt-fragments.js";
|
|
6
7
|
export * from "./bridge-workspace.js";
|
|
7
8
|
export * from "./dispatch-adapter.js";
|
|
8
|
-
export { createLogger } from "./logger.js";
|
|
9
|
+
export { createLogger, childLogger } from "./logger.js";
|
|
9
10
|
export * from "./gateway-base.js";
|
|
10
11
|
export * from "./platform-config.js";
|
|
11
12
|
export { writeSkillFiles, buildSkillReferences, SKILLS } from "./skills/index.js";
|
package/dist/logger.d.ts
CHANGED
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAO1D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAE7E"}
|
package/dist/logger.js
CHANGED
|
@@ -3,5 +3,9 @@ export function createLogger(prefix) {
|
|
|
3
3
|
info: (msg) => console.log(`${new Date().toISOString()} [${prefix}] ${msg}`),
|
|
4
4
|
warn: (msg) => console.warn(`${new Date().toISOString()} [${prefix}] ${msg}`),
|
|
5
5
|
error: (msg) => console.error(`${new Date().toISOString()} [${prefix}] ${msg}`),
|
|
6
|
+
child: (sub) => createLogger(`${prefix}:${sub}`),
|
|
6
7
|
};
|
|
7
8
|
}
|
|
9
|
+
export function childLogger(logger, sub) {
|
|
10
|
+
return logger.child ? logger.child(sub) : logger;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ProviderConfig {
|
|
2
|
+
llm_source?: string;
|
|
3
|
+
openai_api_key?: string;
|
|
4
|
+
openai_base_url?: string;
|
|
5
|
+
anthropic_auth_token?: string;
|
|
6
|
+
anthropic_base_url?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function llmSource(pc?: ProviderConfig): string;
|
|
9
|
+
export declare function clearAllProviderCreds(env: NodeJS.ProcessEnv): void;
|
|
10
|
+
export declare function parseProviderConfig(env: NodeJS.ProcessEnv): ProviderConfig | undefined;
|
|
11
|
+
//# sourceMappingURL=provider-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-config.d.ts","sourceRoot":"","sources":["../src/provider-config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,SAAS,CAAC,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAWrD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAOlE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,SAAS,CAQtF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function llmSource(pc) {
|
|
2
|
+
if (pc?.llm_source)
|
|
3
|
+
return pc.llm_source;
|
|
4
|
+
if (pc?.openai_api_key ||
|
|
5
|
+
pc?.openai_base_url ||
|
|
6
|
+
pc?.anthropic_auth_token ||
|
|
7
|
+
pc?.anthropic_base_url) {
|
|
8
|
+
return "custom";
|
|
9
|
+
}
|
|
10
|
+
return "parall";
|
|
11
|
+
}
|
|
12
|
+
export function clearAllProviderCreds(env) {
|
|
13
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
14
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
15
|
+
delete env.ANTHROPIC_API_KEY;
|
|
16
|
+
delete env.OPENAI_API_KEY;
|
|
17
|
+
delete env.OPENAI_BASE_URL;
|
|
18
|
+
delete env.PRLL_CLAUDE_ALLOW_API_KEY;
|
|
19
|
+
}
|
|
20
|
+
export function parseProviderConfig(env) {
|
|
21
|
+
const raw = env.PRLL_PROVIDER_CONFIG?.trim();
|
|
22
|
+
if (!raw)
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
throw new Error(`Invalid PRLL_PROVIDER_CONFIG JSON: ${String(err)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.0",
|
|
4
4
|
"description": "Shared agent runtime orchestration helpers for Parall",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"src"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@parall/sdk": "1.
|
|
29
|
+
"@parall/sdk": "1.31.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0",
|
package/src/dispatch-adapter.ts
CHANGED
package/src/gateway-base.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { MENTION_ALL_USER_ID, ParallClient, ParallWs } from "@parall/sdk";
|
|
5
5
|
import type {
|
|
6
6
|
AgentConfigUpdateData,
|
|
7
|
+
AgentNewSessionData,
|
|
7
8
|
AgentSessionDB,
|
|
8
9
|
Approval,
|
|
9
10
|
Chat,
|
|
@@ -39,6 +40,8 @@ import {
|
|
|
39
40
|
} from "./session-state.js";
|
|
40
41
|
import type { DispatchState, ParallEvent } from "./types.js";
|
|
41
42
|
|
|
43
|
+
const LIVE_SESSION_STATUSES = new Set(["open", "active", "idle"]);
|
|
44
|
+
|
|
42
45
|
type ChatInfo = {
|
|
43
46
|
type: Chat["type"];
|
|
44
47
|
name: string | null;
|
|
@@ -107,6 +110,8 @@ export type ParallGatewayOptions = {
|
|
|
107
110
|
onSessionReady?: (state: { activeSessionId?: string; ws: ParallWs; runtimeKey: string }) => Promise<void> | void;
|
|
108
111
|
onSessionBinding?: (state: { sessionKey: string; agentSessionId: string; runtimeSessionId: string; runtimeLaneKey: string }) => Promise<void> | void;
|
|
109
112
|
onBeforeDisconnect?: () => Promise<void> | void;
|
|
113
|
+
onNewSession?: (previousSessionId: string) => Promise<void> | void;
|
|
114
|
+
onSessionStale?: (sessionKey: string) => Promise<void> | void;
|
|
110
115
|
};
|
|
111
116
|
|
|
112
117
|
type AgentSessionBinding = {
|
|
@@ -196,6 +201,7 @@ export class ParallAgentGateway {
|
|
|
196
201
|
private shuttingDown = false;
|
|
197
202
|
private inFlightDispatches = 0;
|
|
198
203
|
private drainResolvers: Array<() => void> = [];
|
|
204
|
+
private pendingRestartNotification: string | null = null;
|
|
199
205
|
|
|
200
206
|
private readonly DISPATCHED_MESSAGES_CAP = 5000;
|
|
201
207
|
private readonly COLD_START_WINDOW_MS: number;
|
|
@@ -213,7 +219,7 @@ export class ParallAgentGateway {
|
|
|
213
219
|
const { ws, log } = this.opts;
|
|
214
220
|
|
|
215
221
|
ws.onStateChange((state) => {
|
|
216
|
-
log?.info(`
|
|
222
|
+
log?.info(`connection state → ${state}`);
|
|
217
223
|
});
|
|
218
224
|
|
|
219
225
|
ws.on("hello", async (data: HelloData) => {
|
|
@@ -249,18 +255,33 @@ export class ParallAgentGateway {
|
|
|
249
255
|
});
|
|
250
256
|
|
|
251
257
|
ws.on("agent_config.update", async (data: AgentConfigUpdateData) => {
|
|
252
|
-
this.opts.log?.info(`
|
|
258
|
+
this.opts.log?.info(`config update notification (version=${data.version})`);
|
|
253
259
|
try {
|
|
254
260
|
await this.opts.onConfigUpdate?.(data);
|
|
255
261
|
} catch (err) {
|
|
256
|
-
this.opts.log?.warn(`
|
|
262
|
+
this.opts.log?.warn(`config update failed: ${String(err)}`);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
ws.on("agent.new_session", async (data: AgentNewSessionData) => {
|
|
267
|
+
const prevId = data.previous_session_id ?? "";
|
|
268
|
+
this.opts.log?.info(`new session signal received (previous=${prevId})`);
|
|
269
|
+
this.sessionBindings.clear();
|
|
270
|
+
if (prevId) {
|
|
271
|
+
this.pendingRestartNotification =
|
|
272
|
+
`[Harness Notification] This is a fresh session. Your previous session (${prevId}) was ended by the user and you have been restarted.`;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
await this.opts.onNewSession?.(prevId);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
this.opts.log?.warn(`onNewSession callback failed: ${String(err)}`);
|
|
257
278
|
}
|
|
258
279
|
});
|
|
259
280
|
|
|
260
281
|
ws.on("recovery.overflow", () => {
|
|
261
|
-
this.opts.log?.warn(`
|
|
282
|
+
this.opts.log?.warn(`recovery.overflow — triggering full catch-up`);
|
|
262
283
|
this.catchUpFromDispatch().catch((err) =>
|
|
263
|
-
this.opts.log?.warn(`
|
|
284
|
+
this.opts.log?.warn(`overflow catch-up failed: ${String(err)}`));
|
|
264
285
|
});
|
|
265
286
|
|
|
266
287
|
ws.on("task.assigned", async (data: TaskAssignedData) => {
|
|
@@ -272,7 +293,7 @@ export class ParallAgentGateway {
|
|
|
272
293
|
this.opts.client.ackDispatch(this.opts.config.org_id, { source_type: "task_activity", source_id: data.id }).catch(() => {});
|
|
273
294
|
}
|
|
274
295
|
} catch (err) {
|
|
275
|
-
this.opts.log?.error(`
|
|
296
|
+
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
276
297
|
}
|
|
277
298
|
});
|
|
278
299
|
|
|
@@ -285,7 +306,7 @@ export class ParallAgentGateway {
|
|
|
285
306
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
286
307
|
}
|
|
287
308
|
} catch (err) {
|
|
288
|
-
this.opts.log?.error(`
|
|
309
|
+
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
289
310
|
}
|
|
290
311
|
} else if (data.event_type === "task_update") {
|
|
291
312
|
if (!data.task_id) return;
|
|
@@ -295,7 +316,7 @@ export class ParallAgentGateway {
|
|
|
295
316
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
296
317
|
}
|
|
297
318
|
} catch (err) {
|
|
298
|
-
this.opts.log?.error(`
|
|
319
|
+
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
299
320
|
}
|
|
300
321
|
} else if (data.event_type === "schedule.fire") {
|
|
301
322
|
if (!data.source_id) return;
|
|
@@ -305,7 +326,7 @@ export class ParallAgentGateway {
|
|
|
305
326
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
306
327
|
}
|
|
307
328
|
} catch (err) {
|
|
308
|
-
this.opts.log?.error(`
|
|
329
|
+
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
309
330
|
}
|
|
310
331
|
} else if (data.event_type === "approval_decided") {
|
|
311
332
|
if (!data.source_id) return;
|
|
@@ -315,7 +336,7 @@ export class ParallAgentGateway {
|
|
|
315
336
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
316
337
|
}
|
|
317
338
|
} catch (err) {
|
|
318
|
-
this.opts.log?.error(`
|
|
339
|
+
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
319
340
|
}
|
|
320
341
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
321
342
|
// Truly unknown event_type — log so a newly-added dispatch type
|
|
@@ -325,11 +346,11 @@ export class ParallAgentGateway {
|
|
|
325
346
|
// (message.new, task.assigned) above and would otherwise spam
|
|
326
347
|
// info-level logs for every inbound chat message / task
|
|
327
348
|
// assignment on a busy agent.
|
|
328
|
-
this.opts.log?.info(`
|
|
349
|
+
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) — no-op`);
|
|
329
350
|
}
|
|
330
351
|
});
|
|
331
352
|
|
|
332
|
-
this.opts.log?.info(`
|
|
353
|
+
this.opts.log?.info(`connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
|
|
333
354
|
await ws.connect();
|
|
334
355
|
|
|
335
356
|
return new Promise<void>((resolve) => {
|
|
@@ -462,7 +483,7 @@ export class ParallAgentGateway {
|
|
|
462
483
|
},
|
|
463
484
|
});
|
|
464
485
|
} catch (err) {
|
|
465
|
-
this.opts.log?.warn(`
|
|
486
|
+
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
466
487
|
}
|
|
467
488
|
}
|
|
468
489
|
|
|
@@ -546,12 +567,12 @@ export class ParallAgentGateway {
|
|
|
546
567
|
target_type: target.target_type,
|
|
547
568
|
target_id: target.target_id,
|
|
548
569
|
content: { text: runtimeEvent.message, suppressed: false },
|
|
549
|
-
projection:
|
|
570
|
+
projection: false,
|
|
550
571
|
});
|
|
551
572
|
break;
|
|
552
573
|
}
|
|
553
574
|
} catch (err) {
|
|
554
|
-
this.opts.log?.warn(`
|
|
575
|
+
this.opts.log?.warn(`failed to create ${runtimeEvent.type} step: ${String(err)}`);
|
|
555
576
|
}
|
|
556
577
|
}
|
|
557
578
|
|
|
@@ -560,7 +581,7 @@ export class ParallAgentGateway {
|
|
|
560
581
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
561
582
|
fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
562
583
|
} catch (err) {
|
|
563
|
-
this.opts.log?.warn(`
|
|
584
|
+
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
564
585
|
}
|
|
565
586
|
}
|
|
566
587
|
|
|
@@ -571,7 +592,7 @@ export class ParallAgentGateway {
|
|
|
571
592
|
ctx.step_id = stepId;
|
|
572
593
|
fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
573
594
|
} catch (err) {
|
|
574
|
-
this.opts.log?.warn(`
|
|
595
|
+
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
575
596
|
}
|
|
576
597
|
}
|
|
577
598
|
|
|
@@ -582,7 +603,7 @@ export class ParallAgentGateway {
|
|
|
582
603
|
ctx.session_id = sessionId;
|
|
583
604
|
fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
584
605
|
} catch (err) {
|
|
585
|
-
this.opts.log?.warn(`
|
|
606
|
+
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
586
607
|
}
|
|
587
608
|
}
|
|
588
609
|
|
|
@@ -592,7 +613,7 @@ export class ParallAgentGateway {
|
|
|
592
613
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
593
614
|
fs.writeFileSync(filePath, stepId, "utf8");
|
|
594
615
|
} catch (err) {
|
|
595
|
-
this.opts.log?.warn(`
|
|
616
|
+
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
596
617
|
}
|
|
597
618
|
}
|
|
598
619
|
|
|
@@ -641,6 +662,16 @@ export class ParallAgentGateway {
|
|
|
641
662
|
parent_session_id: parentSessionId,
|
|
642
663
|
runtime_ref: Object.keys(runtimeRef).length > 0 ? runtimeRef : undefined,
|
|
643
664
|
});
|
|
665
|
+
if (!LIVE_SESSION_STATUSES.has(session.status)) {
|
|
666
|
+
this.opts.log?.warn?.(
|
|
667
|
+
`createAgentSession returned terminal session ${session.id} (${session.status}), resetting runtime for ${sessionKey}`,
|
|
668
|
+
);
|
|
669
|
+
this.sessionBindings.delete(sessionKey);
|
|
670
|
+
try { await this.opts.onSessionStale?.(sessionKey); } catch (e) { this.opts.log?.warn?.(`onSessionStale failed: ${e}`); }
|
|
671
|
+
this.opts.log?.info?.(`stale session self-heal complete for ${sessionKey} — next dispatch will create a fresh session`);
|
|
672
|
+
throw new Error(`Agent session ${session.id} is terminal (${session.status})`);
|
|
673
|
+
}
|
|
674
|
+
|
|
644
675
|
const binding: AgentSessionBinding = {
|
|
645
676
|
sessionKey,
|
|
646
677
|
agentSessionId: session.id,
|
|
@@ -675,11 +706,16 @@ export class ParallAgentGateway {
|
|
|
675
706
|
// and are awaited by shutdown() up to SHUTDOWN_DEADLINE_MS.
|
|
676
707
|
if (this.shuttingDown) {
|
|
677
708
|
this.opts.log?.info(
|
|
678
|
-
`
|
|
709
|
+
`skipping dispatch for ${event.messageId} (shutting down) — leaving unacked for catch-up on replacement pod`,
|
|
679
710
|
);
|
|
680
711
|
return false;
|
|
681
712
|
}
|
|
682
713
|
|
|
714
|
+
if (this.pendingRestartNotification) {
|
|
715
|
+
bodyForAgent = this.pendingRestartNotification + "\n\n---\n\n" + bodyForAgent;
|
|
716
|
+
this.pendingRestartNotification = null;
|
|
717
|
+
}
|
|
718
|
+
|
|
683
719
|
setSessionChatId(sessionKey, event.targetId);
|
|
684
720
|
setSessionMessageId(sessionKey, event.messageId);
|
|
685
721
|
setDispatchMessageId(sessionKey, event.messageId);
|
|
@@ -832,7 +868,7 @@ export class ParallAgentGateway {
|
|
|
832
868
|
item.resolve(true);
|
|
833
869
|
}
|
|
834
870
|
} catch (err) {
|
|
835
|
-
this.opts.log?.error(`
|
|
871
|
+
this.opts.log?.error(`fork dispatch failed for ${last.messageId}: ${String(err)}`);
|
|
836
872
|
for (const item of items) {
|
|
837
873
|
item.resolve(false);
|
|
838
874
|
}
|
|
@@ -851,7 +887,7 @@ export class ParallAgentGateway {
|
|
|
851
887
|
try {
|
|
852
888
|
historyPath = this.opts.dispatchAdapter.getSessionHistoryPath?.(fork.fork.sessionKey);
|
|
853
889
|
} catch (err) {
|
|
854
|
-
this.opts.log?.warn?.(`
|
|
890
|
+
this.opts.log?.warn?.(`failed to resolve fork history path: ${String(err)}`);
|
|
855
891
|
}
|
|
856
892
|
this.dispatchState.pendingForkResults.push({
|
|
857
893
|
forkSessionKey: fork.fork.sessionKey,
|
|
@@ -911,7 +947,7 @@ export class ParallAgentGateway {
|
|
|
911
947
|
// pod is the single source of truth for replay.
|
|
912
948
|
if (this.shuttingDown) {
|
|
913
949
|
this.opts.log?.info(
|
|
914
|
-
`
|
|
950
|
+
`drainMainBuffer halted (shutting down) — ${this.dispatchState.mainBuffer.length} buffered, ${this.dispatchState.pendingForkResults.length} pending fork results left for catch-up`,
|
|
915
951
|
);
|
|
916
952
|
break;
|
|
917
953
|
}
|
|
@@ -1006,7 +1042,7 @@ export class ParallAgentGateway {
|
|
|
1006
1042
|
) {
|
|
1007
1043
|
this.startInjectedTyping(event);
|
|
1008
1044
|
this.opts.log?.info(
|
|
1009
|
-
`
|
|
1045
|
+
`steer injected for ${event.messageId} (will drain for bookkeeping)`,
|
|
1010
1046
|
);
|
|
1011
1047
|
}
|
|
1012
1048
|
// If the main dispatch cycle ended while we awaited the steer RPC,
|
|
@@ -1043,7 +1079,7 @@ export class ParallAgentGateway {
|
|
|
1043
1079
|
});
|
|
1044
1080
|
|
|
1045
1081
|
if (!fork) {
|
|
1046
|
-
this.opts.log?.warn(`
|
|
1082
|
+
this.opts.log?.warn(`fork failed, buffering event for main session`);
|
|
1047
1083
|
this.dispatchState.mainBuffer.push(event);
|
|
1048
1084
|
return false;
|
|
1049
1085
|
}
|
|
@@ -1061,7 +1097,7 @@ export class ParallAgentGateway {
|
|
|
1061
1097
|
activeFork.queue.push({ event, resolve });
|
|
1062
1098
|
});
|
|
1063
1099
|
this.runForkDrainLoop(activeFork).catch((err) => {
|
|
1064
|
-
this.opts.log?.error(`
|
|
1100
|
+
this.opts.log?.error(`fork drain loop error: ${String(err)}`);
|
|
1065
1101
|
});
|
|
1066
1102
|
return firstEventPromise;
|
|
1067
1103
|
}
|
|
@@ -1081,7 +1117,7 @@ export class ParallAgentGateway {
|
|
|
1081
1117
|
this.chatInfoMap.set(chatId, chatInfo);
|
|
1082
1118
|
return chatInfo;
|
|
1083
1119
|
} catch (err) {
|
|
1084
|
-
this.opts.log?.warn(`
|
|
1120
|
+
this.opts.log?.warn(`failed to resolve chat ${chatId}: ${String(err)}`);
|
|
1085
1121
|
return null;
|
|
1086
1122
|
}
|
|
1087
1123
|
}
|
|
@@ -1161,7 +1197,7 @@ export class ParallAgentGateway {
|
|
|
1161
1197
|
this.dispatchedMessages.delete(data.id);
|
|
1162
1198
|
}
|
|
1163
1199
|
} catch (err) {
|
|
1164
|
-
this.opts.log?.error(`
|
|
1200
|
+
this.opts.log?.error(`event dispatch failed for ${data.id}: ${String(err)}`);
|
|
1165
1201
|
this.dispatchedMessages.delete(data.id);
|
|
1166
1202
|
}
|
|
1167
1203
|
}
|
|
@@ -1170,11 +1206,11 @@ export class ParallAgentGateway {
|
|
|
1170
1206
|
if (this.shuttingDown) return false; // drain window — let server requeue via catch-up
|
|
1171
1207
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
1172
1208
|
if (this.dispatchedTasks.has(dedupeKey)) {
|
|
1173
|
-
this.opts.log?.info(`
|
|
1209
|
+
this.opts.log?.info(`skipping already-dispatched task ${task.identifier ?? task.id}`);
|
|
1174
1210
|
return false;
|
|
1175
1211
|
}
|
|
1176
1212
|
this.dispatchedTasks.add(dedupeKey);
|
|
1177
|
-
this.opts.log?.info(`
|
|
1213
|
+
this.opts.log?.info(`task assigned: ${task.identifier ?? task.id} "${task.title}"`);
|
|
1178
1214
|
|
|
1179
1215
|
const parts = [`Title: ${task.title}`];
|
|
1180
1216
|
parts.push(`Status: ${task.status}`, `Priority: ${task.priority}`);
|
|
@@ -1220,7 +1256,7 @@ export class ParallAgentGateway {
|
|
|
1220
1256
|
const isCreatorUpdate = opts.allowCreator === true && task.creator_id === this.opts.agentUserId;
|
|
1221
1257
|
if (!isAssignee && !isCreatorUpdate) {
|
|
1222
1258
|
this.opts.log?.info(
|
|
1223
|
-
`
|
|
1259
|
+
`skipping stale task dispatch ${ackSourceId ?? taskId} — assigned to ${task.assignee_id}, creator ${task.creator_id}`,
|
|
1224
1260
|
);
|
|
1225
1261
|
return true;
|
|
1226
1262
|
}
|
|
@@ -1245,7 +1281,7 @@ export class ParallAgentGateway {
|
|
|
1245
1281
|
const status = (err as { status?: number })?.status;
|
|
1246
1282
|
if (status === 404) {
|
|
1247
1283
|
// Comment deleted — ack stale dispatch so it doesn't pile up.
|
|
1248
|
-
this.opts.log?.info(`
|
|
1284
|
+
this.opts.log?.info(`skipping deleted comment ${commentId}, acking stale dispatch`);
|
|
1249
1285
|
this.dispatchedTasks.delete(dedupeKey);
|
|
1250
1286
|
return true; // caller will ack
|
|
1251
1287
|
}
|
|
@@ -1257,7 +1293,7 @@ export class ParallAgentGateway {
|
|
|
1257
1293
|
return true; // null response = gone, ack stale dispatch
|
|
1258
1294
|
}
|
|
1259
1295
|
if (comment.hints?.no_reply) {
|
|
1260
|
-
this.opts.log?.info(`
|
|
1296
|
+
this.opts.log?.info(`skipping no_reply task comment ${commentId}, acking stale dispatch`);
|
|
1261
1297
|
this.dispatchedTasks.delete(dedupeKey);
|
|
1262
1298
|
return true;
|
|
1263
1299
|
}
|
|
@@ -1268,7 +1304,7 @@ export class ParallAgentGateway {
|
|
|
1268
1304
|
} catch { /* task context is optional */ }
|
|
1269
1305
|
|
|
1270
1306
|
const taskLabel = task ? `${task.identifier ?? task.id} "${task.title}"` : taskId;
|
|
1271
|
-
this.opts.log?.info(`
|
|
1307
|
+
this.opts.log?.info(`task comment on ${taskLabel} by ${actorId ?? "unknown"}`);
|
|
1272
1308
|
|
|
1273
1309
|
const parts: string[] = [];
|
|
1274
1310
|
if (task) {
|
|
@@ -1328,10 +1364,10 @@ export class ParallAgentGateway {
|
|
|
1328
1364
|
// already-delivered run — this is expected but operationally
|
|
1329
1365
|
// visible, so warn (not info) keeps it findable during incident
|
|
1330
1366
|
// triage. See docs/primitives/schedule.md § Snapshot 不变性.
|
|
1331
|
-
this.opts.log?.warn(`
|
|
1367
|
+
this.opts.log?.warn(`schedule run ${runId} not accessible (404), acking stale dispatch`);
|
|
1332
1368
|
return true;
|
|
1333
1369
|
}
|
|
1334
|
-
this.opts.log?.warn(`
|
|
1370
|
+
this.opts.log?.warn(`schedule run fetch failed for ${runId}, leaving pending: ${String(err)}`);
|
|
1335
1371
|
return false;
|
|
1336
1372
|
}
|
|
1337
1373
|
if (!run) return true;
|
|
@@ -1343,7 +1379,7 @@ export class ParallAgentGateway {
|
|
|
1343
1379
|
const dedupeKey = `schedule_run:${run.id}`;
|
|
1344
1380
|
if (this.dispatchedTasks.has(dedupeKey)) return false;
|
|
1345
1381
|
this.dispatchedTasks.add(dedupeKey);
|
|
1346
|
-
this.opts.log?.info(`
|
|
1382
|
+
this.opts.log?.info(`schedule fired: ${run.id} (schedule ${run.schedule_id})`);
|
|
1347
1383
|
|
|
1348
1384
|
const event: ParallEvent = {
|
|
1349
1385
|
type: "schedule",
|
|
@@ -1383,10 +1419,10 @@ export class ParallAgentGateway {
|
|
|
1383
1419
|
} catch (err: unknown) {
|
|
1384
1420
|
const status = (err as { status?: number })?.status;
|
|
1385
1421
|
if (status === 404 || status === 403) {
|
|
1386
|
-
this.opts.log?.warn(`
|
|
1422
|
+
this.opts.log?.warn(`approval ${approvalId} not accessible (${status}), acking stale dispatch`);
|
|
1387
1423
|
return true;
|
|
1388
1424
|
}
|
|
1389
|
-
this.opts.log?.warn(`
|
|
1425
|
+
this.opts.log?.warn(`approval fetch failed for ${approvalId}, leaving pending: ${String(err)}`);
|
|
1390
1426
|
return false;
|
|
1391
1427
|
}
|
|
1392
1428
|
if (!approval) return true;
|
|
@@ -1395,7 +1431,7 @@ export class ParallAgentGateway {
|
|
|
1395
1431
|
const dedupeKey = `approval:${approval.id}`;
|
|
1396
1432
|
if (this.dispatchedTasks.has(dedupeKey)) return false;
|
|
1397
1433
|
this.dispatchedTasks.add(dedupeKey);
|
|
1398
|
-
this.opts.log?.info(`
|
|
1434
|
+
this.opts.log?.info(`approval decided: ${approval.id} (${approval.status})`);
|
|
1399
1435
|
|
|
1400
1436
|
const statusLabel = approval.status === "approved" ? "Approved" : "Rejected";
|
|
1401
1437
|
const execInfo = approval.execution_status ? ` | execution: ${approval.execution_status}` : "";
|
|
@@ -1452,14 +1488,14 @@ export class ParallAgentGateway {
|
|
|
1452
1488
|
try {
|
|
1453
1489
|
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id);
|
|
1454
1490
|
} catch (err: unknown) {
|
|
1455
|
-
this.opts.log?.warn(`
|
|
1491
|
+
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1456
1492
|
continue;
|
|
1457
1493
|
}
|
|
1458
1494
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
1459
1495
|
try {
|
|
1460
1496
|
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id, { allowCreator: true });
|
|
1461
1497
|
} catch (err: unknown) {
|
|
1462
|
-
this.opts.log?.warn(`
|
|
1498
|
+
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1463
1499
|
continue;
|
|
1464
1500
|
}
|
|
1465
1501
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
@@ -1480,7 +1516,7 @@ export class ParallAgentGateway {
|
|
|
1480
1516
|
msg = null;
|
|
1481
1517
|
} else {
|
|
1482
1518
|
msgFetchFailed = true;
|
|
1483
|
-
this.opts.log?.warn(`
|
|
1519
|
+
this.opts.log?.warn(`catch-up message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
1484
1520
|
}
|
|
1485
1521
|
}
|
|
1486
1522
|
if (msgFetchFailed) {
|
|
@@ -1510,7 +1546,7 @@ export class ParallAgentGateway {
|
|
|
1510
1546
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {});
|
|
1511
1547
|
}
|
|
1512
1548
|
} catch (err) {
|
|
1513
|
-
this.opts.log?.warn(`
|
|
1549
|
+
this.opts.log?.warn(`catch-up dispatch ${item.id} (${item.event_type}) failed: ${String(err)}`);
|
|
1514
1550
|
}
|
|
1515
1551
|
}
|
|
1516
1552
|
// Stop paginating once shutdown begins — the inner loop already broke,
|
|
@@ -1519,7 +1555,7 @@ export class ParallAgentGateway {
|
|
|
1519
1555
|
} while (cursor);
|
|
1520
1556
|
|
|
1521
1557
|
if (processed > 0 || skippedOld > 0) {
|
|
1522
|
-
this.opts.log?.info(`
|
|
1558
|
+
this.opts.log?.info(`dispatch catch-up: processed ${processed}, skipped ${skippedOld} old item(s)`);
|
|
1523
1559
|
}
|
|
1524
1560
|
}
|
|
1525
1561
|
|
|
@@ -1529,7 +1565,7 @@ export class ParallAgentGateway {
|
|
|
1529
1565
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
1530
1566
|
try {
|
|
1531
1567
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
1532
|
-
log?.info(`
|
|
1568
|
+
log?.info(`WebSocket connected, ${count} chats cached`);
|
|
1533
1569
|
|
|
1534
1570
|
await this.opts.onSessionReady?.({
|
|
1535
1571
|
activeSessionId: this.activeSessionId,
|
|
@@ -1544,7 +1580,7 @@ export class ParallAgentGateway {
|
|
|
1544
1580
|
const expectedMs = intervalSec * 1000;
|
|
1545
1581
|
const drift = now - this.lastHeartbeatAt - expectedMs;
|
|
1546
1582
|
if (drift > 15000) {
|
|
1547
|
-
log?.warn(`
|
|
1583
|
+
log?.warn(`heartbeat drift ${drift}ms — event loop may be blocked`);
|
|
1548
1584
|
}
|
|
1549
1585
|
this.lastHeartbeatAt = now;
|
|
1550
1586
|
if (this.opts.ws.state !== "connected") return;
|
|
@@ -1560,10 +1596,10 @@ export class ParallAgentGateway {
|
|
|
1560
1596
|
const isFirstHello = !this.hadSuccessfulHello;
|
|
1561
1597
|
this.hadSuccessfulHello = true;
|
|
1562
1598
|
this.catchUpFromDispatch(isFirstHello).catch((err) => {
|
|
1563
|
-
log?.warn(`
|
|
1599
|
+
log?.warn(`dispatch catch-up failed: ${String(err)}`);
|
|
1564
1600
|
});
|
|
1565
1601
|
} catch (err) {
|
|
1566
|
-
log?.error(`
|
|
1602
|
+
log?.error(`failed to fetch chats: ${String(err)}`);
|
|
1567
1603
|
}
|
|
1568
1604
|
}
|
|
1569
1605
|
|
|
@@ -1598,15 +1634,15 @@ export class ParallAgentGateway {
|
|
|
1598
1634
|
// via dispatch catch-up after the new pod starts.
|
|
1599
1635
|
if (this.inFlightDispatches > 0) {
|
|
1600
1636
|
this.opts.log?.info(
|
|
1601
|
-
`
|
|
1637
|
+
`draining ${this.inFlightDispatches} in-flight dispatch(es), deadline ${this.SHUTDOWN_DEADLINE_MS}ms`,
|
|
1602
1638
|
);
|
|
1603
1639
|
await this.waitForDrain(this.SHUTDOWN_DEADLINE_MS);
|
|
1604
1640
|
if (this.inFlightDispatches > 0) {
|
|
1605
1641
|
this.opts.log?.warn(
|
|
1606
|
-
`
|
|
1642
|
+
`drain deadline hit; ${this.inFlightDispatches} dispatch(es) still running — they will be killed by process exit`,
|
|
1607
1643
|
);
|
|
1608
1644
|
} else {
|
|
1609
|
-
this.opts.log?.info(`
|
|
1645
|
+
this.opts.log?.info(`drain complete`);
|
|
1610
1646
|
}
|
|
1611
1647
|
}
|
|
1612
1648
|
|
|
@@ -1619,6 +1655,6 @@ export class ParallAgentGateway {
|
|
|
1619
1655
|
await this.opts.onBeforeDisconnect?.();
|
|
1620
1656
|
|
|
1621
1657
|
this.opts.ws.disconnect();
|
|
1622
|
-
this.opts.log?.info(`
|
|
1658
|
+
this.opts.log?.info(`disconnected`);
|
|
1623
1659
|
}
|
|
1624
1660
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./provider-config.js";
|
|
1
2
|
export * from "./types.js";
|
|
2
3
|
export * from "./session-state.js";
|
|
3
4
|
export * from "./routing.js";
|
|
@@ -5,7 +6,7 @@ export * from "./event-format.js";
|
|
|
5
6
|
export * from "./prompt-fragments.js";
|
|
6
7
|
export * from "./bridge-workspace.js";
|
|
7
8
|
export * from "./dispatch-adapter.js";
|
|
8
|
-
export { createLogger } from "./logger.js";
|
|
9
|
+
export { createLogger, childLogger } from "./logger.js";
|
|
9
10
|
export * from "./gateway-base.js";
|
|
10
11
|
export * from "./platform-config.js";
|
|
11
12
|
export { writeSkillFiles, buildSkillReferences, SKILLS } from "./skills/index.js";
|
package/src/logger.ts
CHANGED
|
@@ -5,5 +5,10 @@ export function createLogger(prefix: string): GatewayLogger {
|
|
|
5
5
|
info: (msg: string) => console.log(`${new Date().toISOString()} [${prefix}] ${msg}`),
|
|
6
6
|
warn: (msg: string) => console.warn(`${new Date().toISOString()} [${prefix}] ${msg}`),
|
|
7
7
|
error: (msg: string) => console.error(`${new Date().toISOString()} [${prefix}] ${msg}`),
|
|
8
|
+
child: (sub: string) => createLogger(`${prefix}:${sub}`),
|
|
8
9
|
};
|
|
9
10
|
}
|
|
11
|
+
|
|
12
|
+
export function childLogger(logger: GatewayLogger, sub: string): GatewayLogger {
|
|
13
|
+
return logger.child ? logger.child(sub) : logger;
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ProviderConfig {
|
|
2
|
+
llm_source?: string;
|
|
3
|
+
openai_api_key?: string;
|
|
4
|
+
openai_base_url?: string;
|
|
5
|
+
anthropic_auth_token?: string;
|
|
6
|
+
anthropic_base_url?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function llmSource(pc?: ProviderConfig): string {
|
|
10
|
+
if (pc?.llm_source) return pc.llm_source;
|
|
11
|
+
if (
|
|
12
|
+
pc?.openai_api_key ||
|
|
13
|
+
pc?.openai_base_url ||
|
|
14
|
+
pc?.anthropic_auth_token ||
|
|
15
|
+
pc?.anthropic_base_url
|
|
16
|
+
) {
|
|
17
|
+
return "custom";
|
|
18
|
+
}
|
|
19
|
+
return "parall";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function clearAllProviderCreds(env: NodeJS.ProcessEnv): void {
|
|
23
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
24
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
25
|
+
delete env.ANTHROPIC_API_KEY;
|
|
26
|
+
delete env.OPENAI_API_KEY;
|
|
27
|
+
delete env.OPENAI_BASE_URL;
|
|
28
|
+
delete env.PRLL_CLAUDE_ALLOW_API_KEY;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseProviderConfig(env: NodeJS.ProcessEnv): ProviderConfig | undefined {
|
|
32
|
+
const raw = env.PRLL_PROVIDER_CONFIG?.trim();
|
|
33
|
+
if (!raw) return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw) as ProviderConfig;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new Error(`Invalid PRLL_PROVIDER_CONFIG JSON: ${String(err)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|