@pasko70/pibo 1.11.2 → 1.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/chat/loop-api.js +22 -6
- package/dist/apps/chat-ui/assets/{dist-BBVpFHAq.js → dist-1w_WVrcu.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DBnh8gXR.js → dist-BEd6jKzd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BXyVMdHv.js → dist-BI1eS8pb.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CCGKu-Wj.js → dist-BLdgeEs8.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CuGiEm5l.js → dist-BQmnOdXD.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DeOnZ-pw.js → dist-BmGSbokp.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CE0MvPLM.js → dist-Bwx_CaKF.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BiGfVaXN.js → dist-CRYLB6HZ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DnQYnLQS.js → dist-CoUOMSbW.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-VT4x40uL.js → dist-Dehi8o5p.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-xtnVygdr.js → dist-o1kTkdhi.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-DNeE4HrG.css → index-CYLZe0Y0.css} +1 -1
- package/dist/apps/chat-ui/assets/{index-vcg8JNj9.js → index-DT80TM0S.js} +12 -12
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.11.2.vsix → pibo-vscode-ext-1.11.3.vsix} +0 -0
- package/dist/core/routed-session.js +143 -24
- package/dist/core/runtime-telemetry.js +90 -0
- package/dist/core/runtime.js +1 -0
- package/dist/core/session-router.js +262 -50
- package/dist/gateway/server.js +2 -0
- package/dist/gateway/web.js +1 -0
- package/dist/loops/accounting.js +8 -1
- package/dist/loops/cli.js +1 -1
- package/dist/loops/service.js +167 -27
- package/dist/loops/store.js +229 -17
- package/dist/loops/tools.js +30 -9
- package/dist/runs/registry.js +19 -0
- package/package.json +1 -1
package/dist/loops/service.js
CHANGED
|
@@ -19,7 +19,37 @@ class LoopRunTimeoutError extends Error {
|
|
|
19
19
|
this.name = 'LoopRunTimeoutError';
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
class LoopContinuationInvalidatedError extends Error {
|
|
23
|
+
constructor(message) { super(message); this.name = 'LoopContinuationInvalidatedError'; }
|
|
24
|
+
}
|
|
25
|
+
class LoopSessionError extends Error {
|
|
26
|
+
details;
|
|
27
|
+
constructor(message, details) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.details = details;
|
|
30
|
+
this.name = 'LoopSessionError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
22
33
|
function isUnknownProfileErrorMessage(message) { return /^Unknown profile "[^"]+"/.test(message); }
|
|
34
|
+
function failureKey(details) {
|
|
35
|
+
return details?.code ?? details?.category ?? details?.errorClass ?? 'session-error';
|
|
36
|
+
}
|
|
37
|
+
function failureRecovery(details) {
|
|
38
|
+
if (details?.category === 'quota_exhausted' || details?.code === 'quota_exhausted')
|
|
39
|
+
return 'Restore provider quota or billing, then explicitly restart the Goal.';
|
|
40
|
+
if (details?.category === 'auth' || details?.errorClass === 'provider_auth')
|
|
41
|
+
return 'Repair provider authentication, then explicitly restart the Goal.';
|
|
42
|
+
if (details?.category === 'context_overflow' || details?.errorClass === 'provider_context')
|
|
43
|
+
return 'Reduce or reset the session context, then explicitly restart the Goal.';
|
|
44
|
+
return details?.retryable === false ? 'Resolve the reported non-retryable failure, then explicitly restart the Goal.' : 'Pibo will retry automatically after the recorded backoff.';
|
|
45
|
+
}
|
|
46
|
+
function isTerminalGoalStatus(status) { return status === 'complete' || status === 'blocked' || status === 'budget_limited'; }
|
|
47
|
+
function retryBackoffMs(consecutiveErrors, baseMs, maxMs, jitterRatio, random) {
|
|
48
|
+
const exponent = Math.max(0, Math.min(30, consecutiveErrors - 1));
|
|
49
|
+
const bounded = Math.min(maxMs, baseMs * (2 ** exponent));
|
|
50
|
+
const jitter = bounded * jitterRatio * ((Math.max(0, Math.min(1, random())) * 2) - 1);
|
|
51
|
+
return Math.min(maxMs, Math.max(1, Math.round(bounded + jitter)));
|
|
52
|
+
}
|
|
23
53
|
function isJsonObject(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
|
|
24
54
|
function mergeRunResources(jobResources, runResources) {
|
|
25
55
|
if (!jobResources && !runResources)
|
|
@@ -44,6 +74,11 @@ export class PiboLoopService {
|
|
|
44
74
|
intervalMs;
|
|
45
75
|
maxConcurrentRuns;
|
|
46
76
|
runTimeoutMs;
|
|
77
|
+
retryBackoffBaseMs;
|
|
78
|
+
retryBackoffMaxMs;
|
|
79
|
+
retryBackoffJitterRatio;
|
|
80
|
+
now;
|
|
81
|
+
random;
|
|
47
82
|
timer;
|
|
48
83
|
activeRuns = 0;
|
|
49
84
|
stopped = true;
|
|
@@ -60,9 +95,14 @@ export class PiboLoopService {
|
|
|
60
95
|
this.intervalMs = options.intervalMs ?? 5_000;
|
|
61
96
|
this.maxConcurrentRuns = Math.max(1, options.maxConcurrentRuns ?? 2);
|
|
62
97
|
this.runTimeoutMs = options.runTimeoutMs;
|
|
98
|
+
this.retryBackoffBaseMs = Math.max(1, options.retryBackoffBaseMs ?? 5_000);
|
|
99
|
+
this.retryBackoffMaxMs = Math.max(this.retryBackoffBaseMs, options.retryBackoffMaxMs ?? 5 * 60_000);
|
|
100
|
+
this.retryBackoffJitterRatio = Math.max(0, Math.min(1, options.retryBackoffJitterRatio ?? 0.2));
|
|
101
|
+
this.now = options.now ?? (() => new Date());
|
|
102
|
+
this.random = options.random ?? Math.random;
|
|
63
103
|
}
|
|
64
104
|
start() { if (!this.stopped)
|
|
65
|
-
return; this.stopped = false; this.store.recoverInterruptedRuns(); this.unsubscribeProductEvents = this.options.context.subscribeProductEvents?.((event) => this.handleProductEvent(event)); this.unsubscribeOutputEvents = this.options.context.subscribe((event) => this.handleOutputEvent(event)); this.arm(250); }
|
|
105
|
+
return; this.stopped = false; this.store.recoverInterruptedRuns(); this.repairGoalSessionMetadata(); this.unsubscribeProductEvents = this.options.context.subscribeProductEvents?.((event) => this.handleProductEvent(event)); this.unsubscribeOutputEvents = this.options.context.subscribe((event) => this.handleOutputEvent(event)); this.arm(250); }
|
|
66
106
|
stop() { this.stopped = true; if (this.timer)
|
|
67
107
|
clearTimeout(this.timer); this.timer = undefined; for (const timer of this.browserLeaseHeartbeatTimers.values())
|
|
68
108
|
clearInterval(timer); this.browserLeaseHeartbeatTimers.clear(); this.unsubscribeProductEvents?.(); this.unsubscribeProductEvents = undefined; this.unsubscribeOutputEvents?.(); this.unsubscribeOutputEvents = undefined; this.dataStore.close(); this.store.close(); }
|
|
@@ -71,6 +111,32 @@ export class PiboLoopService {
|
|
|
71
111
|
return undefined; const reserved = await this.reserveAfterBeforeRunEvaluation(job); if (!reserved)
|
|
72
112
|
return undefined; void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon()); return reserved.run; }
|
|
73
113
|
stopJob(id) { const job = this.store.requestStop(id); this.armSoon(); return job; }
|
|
114
|
+
removeJob(id) {
|
|
115
|
+
const job = this.store.getJob(id);
|
|
116
|
+
const removed = this.store.removeJob(id);
|
|
117
|
+
if (removed && job?.mode === 'goal' && job.state.lastPiboSessionId)
|
|
118
|
+
this.clearGoalSessionMetadata(job.state.lastPiboSessionId);
|
|
119
|
+
return removed;
|
|
120
|
+
}
|
|
121
|
+
reopenGoal(id, input) {
|
|
122
|
+
if (!input.confirmed)
|
|
123
|
+
throw new Error('Explicit terminal reopen confirmation is required');
|
|
124
|
+
const job = this.store.getJob(id);
|
|
125
|
+
if (!job || job.mode !== 'goal')
|
|
126
|
+
throw new Error('Goal not found');
|
|
127
|
+
const piboSessionId = job.state.lastPiboSessionId;
|
|
128
|
+
if (!piboSessionId)
|
|
129
|
+
throw new Error('Goal cannot be reopened because it has no originating Pibo Session');
|
|
130
|
+
const runtime = this.options.context.getSessionRuntimeStatus?.(piboSessionId);
|
|
131
|
+
if (runtime && (runtime.disposed || runtime.processing || runtime.streaming || runtime.queuedMessages > 0))
|
|
132
|
+
throw new Error('Goal cannot be reopened while its Pibo Session is active, queued, draining, or disposing');
|
|
133
|
+
const controllerRun = this.options.context.listRuns?.({ includeConsumed: true, includeDetached: true }).find((run) => run.controllerPiboSessionId === piboSessionId && (!['completed', 'failed', 'timed_out', 'cancelled'].includes(run.status) || !run.consumed));
|
|
134
|
+
if (controllerRun)
|
|
135
|
+
throw new Error(`Goal cannot be reopened while controller run ${controllerRun.runId} is active or unconsumed`);
|
|
136
|
+
const reopened = this.store.reopenGoal(id, { actorId: input.actorId });
|
|
137
|
+
this.armSoon();
|
|
138
|
+
return reopened;
|
|
139
|
+
}
|
|
74
140
|
async cancelJob(id) {
|
|
75
141
|
const job = this.store.requestCancel(id);
|
|
76
142
|
if (!job)
|
|
@@ -79,6 +145,19 @@ export class PiboLoopService {
|
|
|
79
145
|
this.armSoon();
|
|
80
146
|
return this.store.getJob(id);
|
|
81
147
|
}
|
|
148
|
+
repairGoalSessionMetadata() {
|
|
149
|
+
for (const session of this.options.context.listSessions?.() ?? []) {
|
|
150
|
+
if (session.metadata?.loopMode === 'goal' && (session.metadata.loopJobId !== undefined || session.metadata.loopRunId !== undefined))
|
|
151
|
+
this.clearGoalSessionMetadata(session.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
clearGoalSessionMetadata(piboSessionId) {
|
|
155
|
+
const session = this.options.context.getSession(piboSessionId);
|
|
156
|
+
if (!session || !this.options.context.updateSession)
|
|
157
|
+
return;
|
|
158
|
+
const { loopJobId: _loopJobId, loopRunId: _loopRunId, ...metadata } = session.metadata ?? {};
|
|
159
|
+
this.options.context.updateSession(piboSessionId, { metadata });
|
|
160
|
+
}
|
|
82
161
|
arm(delayMs) { if (this.stopped)
|
|
83
162
|
return; if (this.timer)
|
|
84
163
|
clearTimeout(this.timer); this.timer = setTimeout(() => void this.tick(), delayMs ?? this.intervalMs); }
|
|
@@ -104,7 +183,8 @@ export class PiboLoopService {
|
|
|
104
183
|
} }
|
|
105
184
|
async reserveAfterBeforeRunEvaluation(job) {
|
|
106
185
|
const fresh = this.store.getJob(job.id) ?? job;
|
|
107
|
-
|
|
186
|
+
const now = this.now();
|
|
187
|
+
if (!fresh.enabled || fresh.state.runningAt || (fresh.state.nextAttemptAt !== undefined && fresh.state.nextAttemptAt > now.toISOString()))
|
|
108
188
|
return undefined;
|
|
109
189
|
const { evaluation, conditionStates } = await this.evaluateStopPolicy(fresh, 'before-run');
|
|
110
190
|
if (evaluation.finalAction !== 'continue') {
|
|
@@ -114,7 +194,7 @@ export class PiboLoopService {
|
|
|
114
194
|
this.store.applyStopEvaluation({ jobId: fresh.id, evaluation, conditionStates, disable: false });
|
|
115
195
|
if (fresh.mode === 'goal' && !await this.renewGoalBrowserLeases(fresh))
|
|
116
196
|
return undefined;
|
|
117
|
-
const reserved = this.store.reserveRun(fresh.id);
|
|
197
|
+
const reserved = this.store.reserveRun(fresh.id, now);
|
|
118
198
|
if (reserved)
|
|
119
199
|
this.startGoalBrowserLeaseHeartbeat(reserved.job, reserved.run);
|
|
120
200
|
return reserved;
|
|
@@ -214,20 +294,65 @@ export class PiboLoopService {
|
|
|
214
294
|
const cancelled = this.cancelledRuns.delete(run.id);
|
|
215
295
|
const outcome = { status: cancelled ? 'cancelled' : 'ok', piboSessionId: result.piboSessionId, finalAnswer: result.finalAnswer };
|
|
216
296
|
const { evaluation, conditionStates } = await this.evaluateStopPolicy(this.store.getJob(job.id) ?? job, 'after-run', run, outcome);
|
|
217
|
-
this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, piboSessionId: result.piboSessionId, reason: cancelled ? 'cancelled' : evaluation.reason, stopAfterRun: evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates });
|
|
297
|
+
this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, piboSessionId: result.piboSessionId, reason: cancelled ? 'cancelled' : evaluation.reason, stopAfterRun: evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates }, this.now());
|
|
218
298
|
await this.cleanupRunResources(job, run);
|
|
219
299
|
}
|
|
220
300
|
catch (error) {
|
|
221
301
|
recordActiveTime();
|
|
222
302
|
const cancelled = this.cancelledRuns.delete(run.id);
|
|
223
303
|
const message = errorMessage(error);
|
|
224
|
-
const
|
|
304
|
+
const invalidated = error instanceof LoopContinuationInvalidatedError;
|
|
305
|
+
const errorDetails = error instanceof LoopSessionError ? error.details : undefined;
|
|
306
|
+
const fatalProfileError = !cancelled && !invalidated && isUnknownProfileErrorMessage(message);
|
|
225
307
|
const timeoutAbortFailed = error instanceof LoopRunTimeoutError && error.abortFailed;
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
|
|
308
|
+
const nonRetryable = !cancelled && !invalidated && errorDetails?.retryable === false;
|
|
309
|
+
const retryable = !cancelled && !invalidated && errorDetails?.retryable !== false;
|
|
310
|
+
const outcome = { status: cancelled ? 'cancelled' : 'error', error: cancelled ? undefined : message, ...(errorDetails ? { errorDetails } : {}) };
|
|
311
|
+
const latestJob = this.store.getJob(job.id) ?? job;
|
|
312
|
+
const latestGoalStatus = latestJob.mode === 'goal' ? latestJob.state.goalStatus ?? (latestJob.enabled ? 'active' : 'paused') : undefined;
|
|
313
|
+
const shouldBlockGoal = nonRetryable && latestJob.mode === 'goal' && !isTerminalGoalStatus(latestGoalStatus);
|
|
314
|
+
const { evaluation, conditionStates } = await this.evaluateStopPolicy(latestJob, 'after-run', run, outcome);
|
|
315
|
+
const automaticRetry = retryable && !fatalProfileError && !timeoutAbortFailed && evaluation.finalAction === 'continue';
|
|
316
|
+
const now = this.now();
|
|
317
|
+
const backoffMs = automaticRetry
|
|
318
|
+
? retryBackoffMs((latestJob.state.consecutiveErrors ?? 0) + 1, this.retryBackoffBaseMs, this.retryBackoffMaxMs, this.retryBackoffJitterRatio, this.random)
|
|
319
|
+
: undefined;
|
|
320
|
+
const nextAttemptAt = backoffMs === undefined ? undefined : new Date(now.getTime() + backoffMs).toISOString();
|
|
321
|
+
const failure = cancelled || invalidated ? undefined : {
|
|
322
|
+
message,
|
|
323
|
+
...(errorDetails ? { details: errorDetails } : {}),
|
|
324
|
+
recovery: automaticRetry ? failureRecovery(errorDetails) : retryable ? 'The configured stop policy stopped automatic retries; restart the Loop explicitly.' : failureRecovery(errorDetails),
|
|
325
|
+
at: now.toISOString(),
|
|
326
|
+
...(nextAttemptAt ? { nextAttemptAt } : {}),
|
|
327
|
+
...(backoffMs !== undefined ? { retryBackoffMs: backoffMs } : {}),
|
|
328
|
+
};
|
|
329
|
+
this.store.completeRun({
|
|
330
|
+
jobId: job.id,
|
|
331
|
+
runId: run.id,
|
|
332
|
+
status: outcome.status,
|
|
333
|
+
error: outcome.error,
|
|
334
|
+
errorDetails,
|
|
335
|
+
failure,
|
|
336
|
+
...(shouldBlockGoal ? { goalStatus: 'blocked' } : {}),
|
|
337
|
+
reason: cancelled
|
|
338
|
+
? 'cancelled'
|
|
339
|
+
: invalidated
|
|
340
|
+
? 'continuation-invalidated'
|
|
341
|
+
: nonRetryable
|
|
342
|
+
? `non-retryable-${failureKey(errorDetails)}`
|
|
343
|
+
: automaticRetry
|
|
344
|
+
? `retry-backoff-${failureKey(errorDetails)}`
|
|
345
|
+
: fatalProfileError
|
|
346
|
+
? 'unknown-profile'
|
|
347
|
+
: timeoutAbortFailed
|
|
348
|
+
? 'timeout-abort-failed'
|
|
349
|
+
: evaluation.reason,
|
|
350
|
+
stopAfterRun: invalidated || nonRetryable || fatalProfileError || timeoutAbortFailed || (!automaticRetry && evaluation.finalAction !== 'continue'),
|
|
351
|
+
stopEvaluation: evaluation,
|
|
352
|
+
conditionStates,
|
|
353
|
+
}, now);
|
|
229
354
|
await this.cleanupRunResources(job, run);
|
|
230
|
-
if (!cancelled)
|
|
355
|
+
if (!cancelled && !invalidated)
|
|
231
356
|
console.error(`[loop] job ${job.id} failed`, error);
|
|
232
357
|
}
|
|
233
358
|
finally {
|
|
@@ -307,18 +432,26 @@ export class PiboLoopService {
|
|
|
307
432
|
}
|
|
308
433
|
getStopConditionDefinitions() { return this.options.context.getLoopStopConditionDefinitions?.() ?? this.options.context.getRalphStopConditionDefinitions?.() ?? createBuiltInLoopStopConditions(); }
|
|
309
434
|
handleOutputEvent(event) {
|
|
435
|
+
const eventId = 'eventId' in event ? event.eventId : undefined;
|
|
436
|
+
if (!eventId)
|
|
437
|
+
return;
|
|
438
|
+
if (event.type === 'message_queued')
|
|
439
|
+
this.store.updateRunMessageState(eventId, 'queued');
|
|
440
|
+
else if (event.type === 'message_started')
|
|
441
|
+
this.store.updateRunMessageState(eventId, 'active');
|
|
442
|
+
else if (event.type === 'message_finished')
|
|
443
|
+
this.store.updateRunMessageState(eventId, 'finished');
|
|
444
|
+
else if (event.type === 'session_error' && event.errorDetails?.code === 'loop_continuation_invalidated')
|
|
445
|
+
this.store.updateRunMessageState(eventId, 'invalidated');
|
|
310
446
|
if (event.type !== 'assistant_usage')
|
|
311
447
|
return;
|
|
312
|
-
const
|
|
313
|
-
if (!
|
|
448
|
+
const run = this.store.getRunByMessageEventId(eventId);
|
|
449
|
+
if (!run || run.piboSessionId !== event.piboSessionId)
|
|
314
450
|
return;
|
|
315
|
-
const
|
|
316
|
-
if (!job
|
|
451
|
+
const job = this.store.getJob(run.jobId);
|
|
452
|
+
if (!job || job.mode !== 'goal')
|
|
317
453
|
return;
|
|
318
|
-
|
|
319
|
-
this.store.recordGoalTurnUsage(job.id, job.state.lastRunId, event.totalTokens);
|
|
320
|
-
else
|
|
321
|
-
this.store.recordGoalProgress(job.id, { tokens: event.totalTokens });
|
|
454
|
+
this.store.recordGoalTurnUsage(job.id, run.id, event.totalTokens);
|
|
322
455
|
}
|
|
323
456
|
handleProductEvent(event) {
|
|
324
457
|
if (event.type !== 'pibo.loop.fact' && event.type !== 'loop.fact' && event.type !== 'pibo.ralph.fact' && event.type !== 'ralph.fact')
|
|
@@ -342,14 +475,15 @@ export class PiboLoopService {
|
|
|
342
475
|
const continuation = reusableSession !== undefined;
|
|
343
476
|
const session = reusableSession ?? this.createLoopSession(job, run);
|
|
344
477
|
if (reusableSession) {
|
|
478
|
+
const { loopJobId: _loopJobId, loopRunId: _loopRunId, ...metadata } = session.metadata ?? {};
|
|
345
479
|
this.options.context.updateSession?.(session.id, {
|
|
346
480
|
title: job.name,
|
|
347
|
-
metadata: { ...
|
|
481
|
+
metadata: { ...metadata, loopMode: job.mode },
|
|
348
482
|
...(job.modelOverride ? { activeModel: { ...job.modelOverride } } : {}),
|
|
349
483
|
});
|
|
350
484
|
}
|
|
351
485
|
this.store.attachRunSession(job.id, run.id, session.id);
|
|
352
|
-
const result = await this.emitMessageAndWait(session.id, buildLoopTurnPrompt(job, continuation, this.goalToolsAvailable(job)));
|
|
486
|
+
const result = await this.emitMessageAndWait(job, run, session.id, buildLoopTurnPrompt(job, continuation, this.goalToolsAvailable(job)));
|
|
353
487
|
return { piboSessionId: session.id, ...result };
|
|
354
488
|
}
|
|
355
489
|
createLoopSession(job, run) {
|
|
@@ -364,8 +498,7 @@ export class PiboLoopService {
|
|
|
364
498
|
metadata: {
|
|
365
499
|
...(target.metadata ?? {}),
|
|
366
500
|
chatRoomId: target.roomId,
|
|
367
|
-
loopJobId: job.id,
|
|
368
|
-
loopRunId: run.id,
|
|
501
|
+
...(job.mode === 'ralph' ? { loopJobId: job.id, loopRunId: run.id } : {}),
|
|
369
502
|
loopMode: job.mode,
|
|
370
503
|
loopTargetKind: job.target.kind,
|
|
371
504
|
...(job.mode === 'ralph' ? { ralphJobId: job.id, ralphRunId: run.id } : {}),
|
|
@@ -392,8 +525,10 @@ export class PiboLoopService {
|
|
|
392
525
|
throw new Error('Target room is archived');
|
|
393
526
|
return { roomId: room.id, workspace: room.workspace ?? getDefaultPiboWorkspace() };
|
|
394
527
|
} const room = this.roomService.ensureDefaultRoom({ name: 'Shared Chat' }); return { roomId: room.id, workspace: room.workspace ?? getDefaultPiboWorkspace() }; }
|
|
395
|
-
async emitMessageAndWait(piboSessionId, text) {
|
|
528
|
+
async emitMessageAndWait(job, run, piboSessionId, text) {
|
|
396
529
|
const eventId = `loop_msg_${randomUUID()}`;
|
|
530
|
+
if (!this.store.attachRunMessage(job.id, run.id, eventId))
|
|
531
|
+
throw new Error(`Loop run ${run.id} is no longer available for message binding`);
|
|
397
532
|
return await new Promise((resolve, reject) => {
|
|
398
533
|
let settled = false;
|
|
399
534
|
let deltaAnswer = '';
|
|
@@ -417,7 +552,7 @@ export class PiboLoopService {
|
|
|
417
552
|
if (this.runTimeoutMs !== undefined) {
|
|
418
553
|
timeout = setTimeout(() => {
|
|
419
554
|
timingOut = true;
|
|
420
|
-
const message = lastSessionError ? `Loop run timed out after session error: ${lastSessionError}` : 'Loop run timed out';
|
|
555
|
+
const message = lastSessionError ? `Loop run timed out after session error: ${lastSessionError.message}` : 'Loop run timed out';
|
|
421
556
|
void this.options.context.emit({ type: 'execution', piboSessionId, action: 'abort', id: `loop_timeout_${randomUUID()}` })
|
|
422
557
|
.then(() => finish(new LoopRunTimeoutError(message)), (abortError) => finish(new LoopRunTimeoutError(`${message}; session abort failed: ${errorMessage(abortError)}`, true)));
|
|
423
558
|
}, this.runTimeoutMs);
|
|
@@ -434,13 +569,18 @@ export class PiboLoopService {
|
|
|
434
569
|
lastSessionError = undefined;
|
|
435
570
|
}
|
|
436
571
|
if (event.type === 'message_finished')
|
|
437
|
-
finish(lastSessionError ? new
|
|
572
|
+
finish(lastSessionError ? new LoopSessionError(lastSessionError.message, lastSessionError.details) : undefined);
|
|
438
573
|
if (event.type === 'session_error') {
|
|
439
|
-
|
|
440
|
-
|
|
574
|
+
if (event.errorDetails?.code === 'loop_continuation_invalidated') {
|
|
575
|
+
finish(new LoopContinuationInvalidatedError(event.error));
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
lastSessionError = { message: event.error, details: event.errorDetails };
|
|
579
|
+
finish(new LoopSessionError(event.error, event.errorDetails));
|
|
580
|
+
}
|
|
441
581
|
}
|
|
442
582
|
});
|
|
443
|
-
this.options.context.emit({ type: 'message', piboSessionId, id: eventId, source: 'service', text }).catch((error) => finish(error instanceof Error ? error : new Error(String(error))));
|
|
583
|
+
this.options.context.emit({ type: 'message', piboSessionId, id: eventId, source: 'service', text, provenance: { kind: 'loop-run', jobId: job.id, runId: run.id } }).catch((error) => finish(error instanceof Error ? error : new Error(String(error))));
|
|
444
584
|
});
|
|
445
585
|
}
|
|
446
586
|
}
|