pi-oracle 0.3.4 → 0.4.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/CHANGELOG.md +21 -0
- package/README.md +2 -0
- package/docs/ORACLE_ISOLATED_PI_VALIDATION.md +249 -0
- package/extensions/oracle/index.ts +8 -1
- package/extensions/oracle/lib/commands.ts +11 -24
- package/extensions/oracle/lib/config.ts +5 -0
- package/extensions/oracle/lib/jobs.ts +117 -217
- package/extensions/oracle/lib/locks.ts +41 -209
- package/extensions/oracle/lib/poller.ts +14 -51
- package/extensions/oracle/lib/queue.ts +75 -112
- package/extensions/oracle/lib/runtime.ts +60 -14
- package/extensions/oracle/lib/tools.ts +66 -65
- package/extensions/oracle/shared/job-coordination-helpers.d.mts +84 -0
- package/extensions/oracle/shared/job-coordination-helpers.mjs +168 -0
- package/extensions/oracle/shared/job-lifecycle-helpers.d.mts +130 -0
- package/extensions/oracle/shared/job-lifecycle-helpers.mjs +377 -0
- package/extensions/oracle/shared/job-observability-helpers.d.mts +59 -0
- package/extensions/oracle/shared/job-observability-helpers.mjs +143 -0
- package/extensions/oracle/shared/process-helpers.d.mts +20 -0
- package/extensions/oracle/shared/process-helpers.mjs +128 -0
- package/extensions/oracle/shared/state-coordination-helpers.d.mts +43 -0
- package/extensions/oracle/shared/state-coordination-helpers.mjs +381 -0
- package/extensions/oracle/worker/artifact-heuristics.mjs +5 -0
- package/extensions/oracle/worker/auth-bootstrap.mjs +76 -130
- package/extensions/oracle/worker/auth-cookie-policy.mjs +5 -0
- package/extensions/oracle/worker/auth-flow-helpers.d.mts +41 -0
- package/extensions/oracle/worker/auth-flow-helpers.mjs +165 -0
- package/extensions/oracle/worker/chatgpt-flow-helpers.d.mts +13 -0
- package/extensions/oracle/worker/chatgpt-flow-helpers.mjs +85 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +93 -9
- package/extensions/oracle/worker/run-job.mjs +166 -274
- package/extensions/oracle/worker/state-locks.mjs +31 -216
- package/package.json +4 -3
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export type OracleJobStatus = "queued" | "preparing" | "submitted" | "waiting" | "complete" | "failed" | "cancelled";
|
|
2
|
+
|
|
3
|
+
export type OracleJobPhase =
|
|
4
|
+
| "queued"
|
|
5
|
+
| "submitted"
|
|
6
|
+
| "cloning_runtime"
|
|
7
|
+
| "launching_browser"
|
|
8
|
+
| "verifying_auth"
|
|
9
|
+
| "configuring_model"
|
|
10
|
+
| "uploading_archive"
|
|
11
|
+
| "awaiting_response"
|
|
12
|
+
| "extracting_response"
|
|
13
|
+
| "downloading_artifacts"
|
|
14
|
+
| "complete"
|
|
15
|
+
| "complete_with_artifact_errors"
|
|
16
|
+
| "failed"
|
|
17
|
+
| "cancelled";
|
|
18
|
+
|
|
19
|
+
export interface OracleJobLifecycleEvent {
|
|
20
|
+
at: string;
|
|
21
|
+
source: string;
|
|
22
|
+
kind: "created" | "phase" | "cleanup" | "notification" | "wakeup";
|
|
23
|
+
status: OracleJobStatus;
|
|
24
|
+
phase: OracleJobPhase;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OracleLifecycleTrackedJobLike {
|
|
29
|
+
status: OracleJobStatus;
|
|
30
|
+
phase: OracleJobPhase;
|
|
31
|
+
phaseAt: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
queuedAt?: string;
|
|
34
|
+
submittedAt?: string;
|
|
35
|
+
completedAt?: string;
|
|
36
|
+
heartbeatAt?: string;
|
|
37
|
+
lifecycleEvents?: OracleJobLifecycleEvent[];
|
|
38
|
+
cleanupPending?: boolean;
|
|
39
|
+
cleanupWarnings?: string[];
|
|
40
|
+
lastCleanupAt?: string;
|
|
41
|
+
notifyClaimedAt?: string;
|
|
42
|
+
notifyClaimedBy?: string;
|
|
43
|
+
notifiedAt?: string;
|
|
44
|
+
notificationEntryId?: string;
|
|
45
|
+
notificationSessionKey?: string;
|
|
46
|
+
notificationSessionFile?: string;
|
|
47
|
+
wakeupAttemptCount?: number;
|
|
48
|
+
wakeupLastRequestedAt?: string;
|
|
49
|
+
wakeupSettledAt?: string;
|
|
50
|
+
wakeupSettledSource?: string;
|
|
51
|
+
wakeupSettledSessionFile?: string;
|
|
52
|
+
wakeupSettledSessionKey?: string;
|
|
53
|
+
wakeupSettledBeforeFirstAttempt?: boolean;
|
|
54
|
+
wakeupObservedAt?: string;
|
|
55
|
+
wakeupObservedSource?: string;
|
|
56
|
+
wakeupObservedSessionFile?: string;
|
|
57
|
+
wakeupObservedSessionKey?: string;
|
|
58
|
+
error?: string;
|
|
59
|
+
artifactFailureCount?: number;
|
|
60
|
+
responsePath?: string;
|
|
61
|
+
responseFormat?: "text/plain";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface OraclePhaseTransitionOptions<TJob extends OracleLifecycleTrackedJobLike> {
|
|
65
|
+
at?: string;
|
|
66
|
+
source?: string;
|
|
67
|
+
message?: string;
|
|
68
|
+
patch?: Partial<TJob>;
|
|
69
|
+
clearNotificationClaim?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface OracleWakeupSettlementOptions {
|
|
73
|
+
source: string;
|
|
74
|
+
at?: string;
|
|
75
|
+
sessionFile?: string;
|
|
76
|
+
sessionKey?: string;
|
|
77
|
+
allowBeforeFirstAttempt?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface OracleNotificationTargetOptions {
|
|
81
|
+
at?: string;
|
|
82
|
+
source?: string;
|
|
83
|
+
notificationSessionKey: string;
|
|
84
|
+
notificationSessionFile?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface OracleMarkNotifiedOptions {
|
|
88
|
+
at?: string;
|
|
89
|
+
source?: string;
|
|
90
|
+
notificationEntryId?: string;
|
|
91
|
+
notificationSessionKey?: string;
|
|
92
|
+
notificationSessionFile?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const ACTIVE_ORACLE_JOB_STATUSES: readonly OracleJobStatus[];
|
|
96
|
+
export const OPEN_ORACLE_JOB_STATUSES: readonly OracleJobStatus[];
|
|
97
|
+
export const TERMINAL_ORACLE_JOB_STATUSES: readonly OracleJobStatus[];
|
|
98
|
+
export const MAX_ORACLE_JOB_LIFECYCLE_EVENTS: number;
|
|
99
|
+
|
|
100
|
+
export declare function getOracleJobStatusForPhase(phase: OracleJobPhase): OracleJobStatus;
|
|
101
|
+
export declare function assertValidOracleJobState<TJob extends OracleLifecycleTrackedJobLike>(job: TJob): TJob;
|
|
102
|
+
export declare function appendOracleJobLifecycleEvent<TJob extends OracleLifecycleTrackedJobLike>(
|
|
103
|
+
job: TJob,
|
|
104
|
+
event: Omit<OracleJobLifecycleEvent, "status" | "phase"> & { status?: OracleJobStatus; phase?: OracleJobPhase },
|
|
105
|
+
): TJob;
|
|
106
|
+
export declare function getLatestOracleJobLifecycleEvent(job: Pick<OracleLifecycleTrackedJobLike, "lifecycleEvents">): OracleJobLifecycleEvent | undefined;
|
|
107
|
+
export declare function markOracleJobCreated<TJob extends OracleLifecycleTrackedJobLike>(job: TJob, options?: { at?: string; source?: string; message?: string }): TJob;
|
|
108
|
+
export declare function transitionOracleJobPhase<TJob extends OracleLifecycleTrackedJobLike>(
|
|
109
|
+
job: TJob,
|
|
110
|
+
phase: OracleJobPhase,
|
|
111
|
+
options?: OraclePhaseTransitionOptions<TJob>,
|
|
112
|
+
): TJob;
|
|
113
|
+
export declare function applyOracleJobCleanupWarnings<TJob extends OracleLifecycleTrackedJobLike>(
|
|
114
|
+
job: TJob,
|
|
115
|
+
warnings: string[],
|
|
116
|
+
options?: { at?: string; source?: string; message?: string },
|
|
117
|
+
): TJob;
|
|
118
|
+
export declare function clearOracleJobCleanupState<TJob extends OracleLifecycleTrackedJobLike>(
|
|
119
|
+
job: TJob,
|
|
120
|
+
options?: { at?: string; source?: string; message?: string },
|
|
121
|
+
): TJob;
|
|
122
|
+
export declare function claimOracleJobNotification<TJob extends OracleLifecycleTrackedJobLike>(job: TJob, claimedBy: string, at?: string): TJob;
|
|
123
|
+
export declare function recordOracleJobNotificationTarget<TJob extends OracleLifecycleTrackedJobLike>(job: TJob, options: OracleNotificationTargetOptions): TJob;
|
|
124
|
+
export declare function markOracleJobNotified<TJob extends OracleLifecycleTrackedJobLike>(job: TJob, options?: OracleMarkNotifiedOptions): TJob;
|
|
125
|
+
export declare function releaseOracleJobNotificationClaim<TJob extends OracleLifecycleTrackedJobLike>(job: TJob): TJob;
|
|
126
|
+
export declare function noteOracleJobWakeupRequested<TJob extends OracleLifecycleTrackedJobLike>(job: TJob, options?: { at?: string; source?: string }): TJob;
|
|
127
|
+
export declare function markOracleJobWakeupSettled<TJob extends OracleLifecycleTrackedJobLike>(
|
|
128
|
+
job: TJob,
|
|
129
|
+
options: OracleWakeupSettlementOptions,
|
|
130
|
+
): TJob;
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// Purpose: Centralize oracle job lifecycle state transitions, invariants, and durable transition breadcrumbs.
|
|
2
|
+
// Responsibilities: Define valid phase/status relationships, apply lifecycle mutations, append bounded lifecycle events, and normalize cleanup/notification/wake-up state changes.
|
|
3
|
+
// Scope: Pure job-state reducers only; persistence, locking, browser work, and UI delivery stay in higher-level modules.
|
|
4
|
+
// Usage: Imported by extension lib code and worker code so all lifecycle transitions share the same invariants and event semantics.
|
|
5
|
+
// Invariants/Assumptions: Phase/status pairs must stay aligned, terminal jobs own completedAt, and cleanupPending is only legal for terminal states.
|
|
6
|
+
|
|
7
|
+
/** @typedef {import("./job-lifecycle-helpers.d.mts").OracleJobLifecycleEvent} OracleJobLifecycleEvent */
|
|
8
|
+
/** @typedef {import("./job-lifecycle-helpers.d.mts").OracleJobPhase} OracleJobPhase */
|
|
9
|
+
/** @typedef {import("./job-lifecycle-helpers.d.mts").OracleJobStatus} OracleJobStatus */
|
|
10
|
+
/** @typedef {import("./job-lifecycle-helpers.d.mts").OracleLifecycleTrackedJobLike} OracleLifecycleTrackedJobLike */
|
|
11
|
+
|
|
12
|
+
export const ACTIVE_ORACLE_JOB_STATUSES = Object.freeze(["preparing", "submitted", "waiting"]);
|
|
13
|
+
export const OPEN_ORACLE_JOB_STATUSES = Object.freeze(["queued", ...ACTIVE_ORACLE_JOB_STATUSES]);
|
|
14
|
+
export const TERMINAL_ORACLE_JOB_STATUSES = Object.freeze(["complete", "failed", "cancelled"]);
|
|
15
|
+
export const MAX_ORACLE_JOB_LIFECYCLE_EVENTS = 64;
|
|
16
|
+
|
|
17
|
+
/** @type {Record<OracleJobPhase, OracleJobStatus>} */
|
|
18
|
+
const PHASE_STATUS = Object.freeze({
|
|
19
|
+
queued: "queued",
|
|
20
|
+
submitted: "submitted",
|
|
21
|
+
cloning_runtime: "waiting",
|
|
22
|
+
launching_browser: "waiting",
|
|
23
|
+
verifying_auth: "waiting",
|
|
24
|
+
configuring_model: "waiting",
|
|
25
|
+
uploading_archive: "waiting",
|
|
26
|
+
awaiting_response: "waiting",
|
|
27
|
+
extracting_response: "waiting",
|
|
28
|
+
downloading_artifacts: "waiting",
|
|
29
|
+
complete: "complete",
|
|
30
|
+
complete_with_artifact_errors: "complete",
|
|
31
|
+
failed: "failed",
|
|
32
|
+
cancelled: "cancelled",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {OracleJobPhase} phase
|
|
37
|
+
* @returns {OracleJobStatus}
|
|
38
|
+
*/
|
|
39
|
+
export function getOracleJobStatusForPhase(phase) {
|
|
40
|
+
return PHASE_STATUS[phase];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
45
|
+
* @param {TJob} job
|
|
46
|
+
* @returns {TJob}
|
|
47
|
+
*/
|
|
48
|
+
export function assertValidOracleJobState(job) {
|
|
49
|
+
const expectedStatus = PHASE_STATUS[job.phase];
|
|
50
|
+
if (!expectedStatus) {
|
|
51
|
+
throw new Error(`Invalid oracle job state: unknown phase ${String(job.phase)}`);
|
|
52
|
+
}
|
|
53
|
+
if (job.status !== expectedStatus) {
|
|
54
|
+
throw new Error(`Invalid oracle job state: phase ${job.phase} requires status ${expectedStatus}, got ${job.status}`);
|
|
55
|
+
}
|
|
56
|
+
if (job.status === "queued" && !job.queuedAt) {
|
|
57
|
+
throw new Error("Invalid oracle job state: queued jobs must record queuedAt");
|
|
58
|
+
}
|
|
59
|
+
if (["submitted", "waiting"].includes(job.status) && !job.submittedAt) {
|
|
60
|
+
throw new Error(`Invalid oracle job state: ${job.status} jobs must record submittedAt`);
|
|
61
|
+
}
|
|
62
|
+
if (TERMINAL_ORACLE_JOB_STATUSES.includes(job.status) && !job.completedAt) {
|
|
63
|
+
throw new Error(`Invalid oracle job state: terminal job ${job.status} must record completedAt`);
|
|
64
|
+
}
|
|
65
|
+
if (job.completedAt && !TERMINAL_ORACLE_JOB_STATUSES.includes(job.status)) {
|
|
66
|
+
throw new Error(`Invalid oracle job state: non-terminal job ${job.status} cannot record completedAt`);
|
|
67
|
+
}
|
|
68
|
+
if (job.cleanupPending && !TERMINAL_ORACLE_JOB_STATUSES.includes(job.status)) {
|
|
69
|
+
throw new Error(`Invalid oracle job state: non-terminal job ${job.status} cannot be cleanupPending`);
|
|
70
|
+
}
|
|
71
|
+
return job;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {Pick<OracleLifecycleTrackedJobLike, "phase" | "status" | "lifecycleEvents">} job
|
|
76
|
+
* @param {Omit<OracleJobLifecycleEvent, "status" | "phase"> & { status?: OracleJobStatus; phase?: OracleJobPhase }} event
|
|
77
|
+
* @returns {OracleJobLifecycleEvent[]}
|
|
78
|
+
*/
|
|
79
|
+
function nextLifecycleEvents(job, event) {
|
|
80
|
+
const entry = {
|
|
81
|
+
at: event.at,
|
|
82
|
+
source: event.source,
|
|
83
|
+
kind: event.kind,
|
|
84
|
+
message: event.message,
|
|
85
|
+
status: event.status ?? job.status,
|
|
86
|
+
phase: event.phase ?? job.phase,
|
|
87
|
+
};
|
|
88
|
+
const events = [...(job.lifecycleEvents || []), entry];
|
|
89
|
+
return events.slice(-MAX_ORACLE_JOB_LIFECYCLE_EVENTS);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
94
|
+
* @param {TJob} job
|
|
95
|
+
* @param {Omit<OracleJobLifecycleEvent, "status" | "phase"> & { status?: OracleJobStatus; phase?: OracleJobPhase }} event
|
|
96
|
+
* @returns {TJob}
|
|
97
|
+
*/
|
|
98
|
+
export function appendOracleJobLifecycleEvent(job, event) {
|
|
99
|
+
return assertValidOracleJobState({
|
|
100
|
+
...job,
|
|
101
|
+
lifecycleEvents: nextLifecycleEvents(job, event),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {Pick<OracleLifecycleTrackedJobLike, "lifecycleEvents">} job
|
|
107
|
+
* @returns {OracleJobLifecycleEvent | undefined}
|
|
108
|
+
*/
|
|
109
|
+
export function getLatestOracleJobLifecycleEvent(job) {
|
|
110
|
+
const events = job.lifecycleEvents || [];
|
|
111
|
+
return events.length > 0 ? events[events.length - 1] : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
116
|
+
* @param {TJob} job
|
|
117
|
+
* @param {{ at?: string; source?: string; message?: string }} [options]
|
|
118
|
+
* @returns {TJob}
|
|
119
|
+
*/
|
|
120
|
+
export function markOracleJobCreated(job, options = {}) {
|
|
121
|
+
const at = options.at ?? job.createdAt;
|
|
122
|
+
return appendOracleJobLifecycleEvent(assertValidOracleJobState(job), {
|
|
123
|
+
at,
|
|
124
|
+
source: options.source ?? "oracle:create",
|
|
125
|
+
kind: "created",
|
|
126
|
+
message: options.message ?? `Job created in ${job.status} state.`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
132
|
+
* @param {TJob} job
|
|
133
|
+
* @param {OracleJobPhase} phase
|
|
134
|
+
* @param {{ at?: string; source?: string; message?: string; patch?: Partial<TJob>; clearNotificationClaim?: boolean }} [options]
|
|
135
|
+
* @returns {TJob}
|
|
136
|
+
*/
|
|
137
|
+
export function transitionOracleJobPhase(job, phase, options = {}) {
|
|
138
|
+
const at = options.at ?? new Date().toISOString();
|
|
139
|
+
const patch = options.patch || {};
|
|
140
|
+
const status = patch.status ?? getOracleJobStatusForPhase(phase);
|
|
141
|
+
if (status !== getOracleJobStatusForPhase(phase)) {
|
|
142
|
+
throw new Error(`Invalid oracle job transition: phase ${phase} requires status ${getOracleJobStatusForPhase(phase)}, got ${String(status)}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @type {TJob} */
|
|
146
|
+
const next = {
|
|
147
|
+
...job,
|
|
148
|
+
...patch,
|
|
149
|
+
status,
|
|
150
|
+
phase,
|
|
151
|
+
phaseAt: at,
|
|
152
|
+
...(phase === "queued" ? { queuedAt: patch.queuedAt ?? job.queuedAt ?? at } : {}),
|
|
153
|
+
...(["submitted", "waiting"].includes(status) || patch.submittedAt !== undefined || job.submittedAt !== undefined
|
|
154
|
+
? { submittedAt: patch.submittedAt ?? job.submittedAt ?? at }
|
|
155
|
+
: {}),
|
|
156
|
+
...(TERMINAL_ORACLE_JOB_STATUSES.includes(status)
|
|
157
|
+
? { completedAt: patch.completedAt ?? job.completedAt ?? at }
|
|
158
|
+
: { completedAt: patch.completedAt ?? job.completedAt }),
|
|
159
|
+
...(options.clearNotificationClaim
|
|
160
|
+
? { notifyClaimedAt: undefined, notifyClaimedBy: undefined }
|
|
161
|
+
: {}),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const validated = assertValidOracleJobState(next);
|
|
165
|
+
const changed = job.phase !== validated.phase || job.status !== validated.status || Boolean(options.message);
|
|
166
|
+
if (!changed) return validated;
|
|
167
|
+
|
|
168
|
+
return appendOracleJobLifecycleEvent(validated, {
|
|
169
|
+
at,
|
|
170
|
+
source: options.source ?? "oracle:lifecycle",
|
|
171
|
+
kind: "phase",
|
|
172
|
+
message: options.message ?? `Transitioned to ${validated.phase} (${validated.status}).`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
178
|
+
* @param {TJob} job
|
|
179
|
+
* @param {string[]} warnings
|
|
180
|
+
* @param {{ at?: string; source?: string; message?: string }} [options]
|
|
181
|
+
* @returns {TJob}
|
|
182
|
+
*/
|
|
183
|
+
export function applyOracleJobCleanupWarnings(job, warnings, options = {}) {
|
|
184
|
+
if (warnings.length === 0) return assertValidOracleJobState(job);
|
|
185
|
+
const at = options.at ?? new Date().toISOString();
|
|
186
|
+
const next = assertValidOracleJobState({
|
|
187
|
+
...job,
|
|
188
|
+
cleanupPending: false,
|
|
189
|
+
cleanupWarnings: Array.from(new Set([...(job.cleanupWarnings || []), ...warnings])),
|
|
190
|
+
lastCleanupAt: at,
|
|
191
|
+
error: [job.error, ...warnings].filter(Boolean).join("\n"),
|
|
192
|
+
});
|
|
193
|
+
return appendOracleJobLifecycleEvent(next, {
|
|
194
|
+
at,
|
|
195
|
+
source: options.source ?? "oracle:cleanup",
|
|
196
|
+
kind: "cleanup",
|
|
197
|
+
message: options.message ?? `Cleanup completed with ${warnings.length} warning(s).`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
203
|
+
* @param {TJob} job
|
|
204
|
+
* @param {{ at?: string; source?: string; message?: string }} [options]
|
|
205
|
+
* @returns {TJob}
|
|
206
|
+
*/
|
|
207
|
+
export function clearOracleJobCleanupState(job, options = {}) {
|
|
208
|
+
const at = options.at ?? new Date().toISOString();
|
|
209
|
+
const next = assertValidOracleJobState({
|
|
210
|
+
...job,
|
|
211
|
+
cleanupPending: false,
|
|
212
|
+
cleanupWarnings: undefined,
|
|
213
|
+
lastCleanupAt: at,
|
|
214
|
+
});
|
|
215
|
+
return appendOracleJobLifecycleEvent(next, {
|
|
216
|
+
at,
|
|
217
|
+
source: options.source ?? "oracle:cleanup",
|
|
218
|
+
kind: "cleanup",
|
|
219
|
+
message: options.message ?? "Cleanup finished without warnings.",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
225
|
+
* @param {TJob} job
|
|
226
|
+
* @param {string} claimedBy
|
|
227
|
+
* @param {string} [at]
|
|
228
|
+
* @returns {TJob}
|
|
229
|
+
*/
|
|
230
|
+
export function claimOracleJobNotification(job, claimedBy, at = new Date().toISOString()) {
|
|
231
|
+
return assertValidOracleJobState({
|
|
232
|
+
...job,
|
|
233
|
+
notifyClaimedBy: claimedBy,
|
|
234
|
+
notifyClaimedAt: at,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
240
|
+
* @param {TJob} job
|
|
241
|
+
* @param {{ at?: string; source?: string; notificationSessionKey: string; notificationSessionFile?: string }} options
|
|
242
|
+
* @returns {TJob}
|
|
243
|
+
*/
|
|
244
|
+
export function recordOracleJobNotificationTarget(job, options) {
|
|
245
|
+
const at = options.at ?? new Date().toISOString();
|
|
246
|
+
const next = assertValidOracleJobState({
|
|
247
|
+
...job,
|
|
248
|
+
notificationSessionKey: options.notificationSessionKey,
|
|
249
|
+
notificationSessionFile: options.notificationSessionFile,
|
|
250
|
+
});
|
|
251
|
+
return appendOracleJobLifecycleEvent(next, {
|
|
252
|
+
at,
|
|
253
|
+
source: options.source ?? "oracle:poller",
|
|
254
|
+
kind: "notification",
|
|
255
|
+
message: `Notification target recorded for ${options.notificationSessionKey}.`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
261
|
+
* @param {TJob} job
|
|
262
|
+
* @param {{ at?: string; source?: string; notificationEntryId?: string; notificationSessionKey?: string; notificationSessionFile?: string }} [options]
|
|
263
|
+
* @returns {TJob}
|
|
264
|
+
*/
|
|
265
|
+
export function markOracleJobNotified(job, options = {}) {
|
|
266
|
+
const at = options.at ?? new Date().toISOString();
|
|
267
|
+
const next = assertValidOracleJobState({
|
|
268
|
+
...job,
|
|
269
|
+
notifiedAt: at,
|
|
270
|
+
notificationEntryId: options.notificationEntryId ?? job.notificationEntryId,
|
|
271
|
+
notificationSessionKey: options.notificationSessionKey ?? job.notificationSessionKey,
|
|
272
|
+
notificationSessionFile: options.notificationSessionFile ?? job.notificationSessionFile,
|
|
273
|
+
wakeupAttemptCount: 0,
|
|
274
|
+
wakeupLastRequestedAt: undefined,
|
|
275
|
+
wakeupSettledAt: undefined,
|
|
276
|
+
notifyClaimedAt: undefined,
|
|
277
|
+
notifyClaimedBy: undefined,
|
|
278
|
+
});
|
|
279
|
+
return appendOracleJobLifecycleEvent(next, {
|
|
280
|
+
at,
|
|
281
|
+
source: options.source ?? "oracle:poller",
|
|
282
|
+
kind: "notification",
|
|
283
|
+
message: "Notification delivery recorded.",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
289
|
+
* @param {TJob} job
|
|
290
|
+
* @returns {TJob}
|
|
291
|
+
*/
|
|
292
|
+
export function releaseOracleJobNotificationClaim(job) {
|
|
293
|
+
return assertValidOracleJobState({
|
|
294
|
+
...job,
|
|
295
|
+
notifyClaimedAt: undefined,
|
|
296
|
+
notifyClaimedBy: undefined,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
302
|
+
* @param {TJob} job
|
|
303
|
+
* @param {{ at?: string; source?: string }} [options]
|
|
304
|
+
* @returns {TJob}
|
|
305
|
+
*/
|
|
306
|
+
export function noteOracleJobWakeupRequested(job, options = {}) {
|
|
307
|
+
const at = options.at ?? new Date().toISOString();
|
|
308
|
+
const next = assertValidOracleJobState({
|
|
309
|
+
...job,
|
|
310
|
+
wakeupAttemptCount: (job.wakeupAttemptCount ?? 0) + 1,
|
|
311
|
+
wakeupLastRequestedAt: at,
|
|
312
|
+
});
|
|
313
|
+
return appendOracleJobLifecycleEvent(next, {
|
|
314
|
+
at,
|
|
315
|
+
source: options.source ?? "oracle:poller",
|
|
316
|
+
kind: "wakeup",
|
|
317
|
+
message: `Wake-up reminder requested (attempt ${next.wakeupAttemptCount}).`,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @template {OracleLifecycleTrackedJobLike} TJob
|
|
323
|
+
* @param {TJob} job
|
|
324
|
+
* @param {{ source: string; at?: string; sessionFile?: string; sessionKey?: string; allowBeforeFirstAttempt?: boolean }} options
|
|
325
|
+
* @returns {TJob}
|
|
326
|
+
*/
|
|
327
|
+
export function markOracleJobWakeupSettled(job, options) {
|
|
328
|
+
const at = options.at ?? new Date().toISOString();
|
|
329
|
+
const beforeFirstAttempt = !job.wakeupLastRequestedAt && (job.wakeupAttemptCount ?? 0) === 0;
|
|
330
|
+
|
|
331
|
+
if (job.wakeupSettledAt) {
|
|
332
|
+
const next = assertValidOracleJobState({
|
|
333
|
+
...job,
|
|
334
|
+
wakeupSettledSource: job.wakeupSettledSource ?? options.source,
|
|
335
|
+
wakeupSettledSessionFile: job.wakeupSettledSessionFile ?? options.sessionFile,
|
|
336
|
+
wakeupSettledSessionKey: job.wakeupSettledSessionKey ?? options.sessionKey,
|
|
337
|
+
wakeupSettledBeforeFirstAttempt: job.wakeupSettledBeforeFirstAttempt ?? beforeFirstAttempt,
|
|
338
|
+
});
|
|
339
|
+
return appendOracleJobLifecycleEvent(next, {
|
|
340
|
+
at,
|
|
341
|
+
source: options.source,
|
|
342
|
+
kind: "wakeup",
|
|
343
|
+
message: `Wake-up already settled via ${next.wakeupSettledSource ?? options.source}.`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (beforeFirstAttempt && !options.allowBeforeFirstAttempt) {
|
|
348
|
+
const observed = assertValidOracleJobState({
|
|
349
|
+
...job,
|
|
350
|
+
wakeupObservedAt: job.wakeupObservedAt ?? at,
|
|
351
|
+
wakeupObservedSource: job.wakeupObservedSource ?? options.source,
|
|
352
|
+
wakeupObservedSessionFile: job.wakeupObservedSessionFile ?? options.sessionFile,
|
|
353
|
+
wakeupObservedSessionKey: job.wakeupObservedSessionKey ?? options.sessionKey,
|
|
354
|
+
});
|
|
355
|
+
return appendOracleJobLifecycleEvent(observed, {
|
|
356
|
+
at,
|
|
357
|
+
source: options.source,
|
|
358
|
+
kind: "wakeup",
|
|
359
|
+
message: `Wake-up observed before the first reminder attempt via ${options.source}.`,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const settled = assertValidOracleJobState({
|
|
364
|
+
...job,
|
|
365
|
+
wakeupSettledAt: at,
|
|
366
|
+
wakeupSettledSource: options.source,
|
|
367
|
+
wakeupSettledSessionFile: options.sessionFile,
|
|
368
|
+
wakeupSettledSessionKey: options.sessionKey,
|
|
369
|
+
wakeupSettledBeforeFirstAttempt: beforeFirstAttempt,
|
|
370
|
+
});
|
|
371
|
+
return appendOracleJobLifecycleEvent(settled, {
|
|
372
|
+
at,
|
|
373
|
+
source: options.source,
|
|
374
|
+
kind: "wakeup",
|
|
375
|
+
message: `Wake-up settled via ${options.source}.`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { OracleJobLifecycleEvent } from "./job-lifecycle-helpers.d.mts";
|
|
2
|
+
|
|
3
|
+
export interface OracleJobSummaryLike {
|
|
4
|
+
id: string;
|
|
5
|
+
status: string;
|
|
6
|
+
phase: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
queuedAt?: string;
|
|
9
|
+
submittedAt?: string;
|
|
10
|
+
completedAt?: string;
|
|
11
|
+
projectId: string;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
followUpToJobId?: string;
|
|
14
|
+
chatUrl?: string;
|
|
15
|
+
conversationId?: string;
|
|
16
|
+
responsePath?: string;
|
|
17
|
+
responseFormat?: string;
|
|
18
|
+
artifactFailureCount?: number;
|
|
19
|
+
lastCleanupAt?: string;
|
|
20
|
+
cleanupWarnings?: string[];
|
|
21
|
+
error?: string;
|
|
22
|
+
workerLogPath?: string;
|
|
23
|
+
lifecycleEvents?: OracleJobLifecycleEvent[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OracleQueuePositionLike {
|
|
27
|
+
position: number;
|
|
28
|
+
depth: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OracleJobSummaryOptions {
|
|
32
|
+
queuePosition?: OracleQueuePositionLike;
|
|
33
|
+
artifactsPath?: string;
|
|
34
|
+
responsePreview?: string;
|
|
35
|
+
includeLatestEvent?: boolean;
|
|
36
|
+
includeWorkerLogPath?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OracleSubmitResponseOptions {
|
|
40
|
+
autoPrunedPrefixes: Array<{ relativePath: string; bytes: number }>;
|
|
41
|
+
queued: boolean;
|
|
42
|
+
queuePosition?: number;
|
|
43
|
+
queueDepth?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface OracleStatusCounts {
|
|
47
|
+
active: number;
|
|
48
|
+
queued: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export declare function formatBytes(bytes: number): string;
|
|
52
|
+
export declare function formatOracleLifecycleEvent(event: OracleJobLifecycleEvent | undefined): string | undefined;
|
|
53
|
+
export declare function formatOracleJobSummary(job: OracleJobSummaryLike, options?: OracleJobSummaryOptions): string;
|
|
54
|
+
export declare function buildOracleWakeupNotificationContent(
|
|
55
|
+
job: OracleJobSummaryLike,
|
|
56
|
+
options?: { responsePath?: string; artifactsPath?: string },
|
|
57
|
+
): string;
|
|
58
|
+
export declare function formatOracleSubmitResponse(job: OracleJobSummaryLike & { promptPath: string; archivePath: string }, options: OracleSubmitResponseOptions): string;
|
|
59
|
+
export declare function buildOracleStatusText(counts: OracleStatusCounts): string;
|