@pasko70/pibo 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -6
- package/dist/agent-runtime/context-build.js +26 -2
- package/dist/agent-runtime/routed-session.js +108 -9
- package/dist/agent-runtimes/codex-native/adapter.js +12 -0
- package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
- package/dist/agent-runtimes/omp/adapter.js +3 -7
- package/dist/agent-runtimes/pi/adapter.js +5 -3
- package/dist/agent-runtimes/pi/routed-session.js +49 -8
- package/dist/agent-runtimes/pi/runtime.js +6 -0
- package/dist/apps/chat/agent-profiles.js +31 -2
- package/dist/apps/chat/agent-store.js +43 -3
- package/dist/apps/chat/chat-request-normalizers.js +31 -0
- package/dist/apps/chat/data/chat-data-mappers.js +61 -11
- package/dist/apps/chat/data/session-query-service.js +33 -10
- package/dist/apps/chat/data/timeline-query-service.js +60 -35
- package/dist/apps/chat/output-compactor.js +144 -21
- package/dist/apps/chat/output-event-policy.js +179 -2
- package/dist/apps/chat/stream.js +16 -7
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/web-app.js +236 -64
- package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-B6GZgWSj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-BPotHecb.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-BzqQKeVO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CvaPTBTN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-paagejNj.js} +1 -1
- package/dist/apps/chat-ui/assets/index-6JJV-eic.js +228 -0
- package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/{index-Dtw2Z7jz.js → index-U-MSErGa.js} +4 -4
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
- package/dist/cli-session/localSessionSource.js +153 -161
- package/dist/cli.js +7 -11
- package/dist/core/context-build.js +7 -16
- package/dist/core/output-persistence-retry.js +484 -0
- package/dist/core/output-render-sequence.js +331 -0
- package/dist/core/profiles.js +24 -0
- package/dist/core/provider-recovery.js +7 -1
- package/dist/core/session-model.js +37 -1
- package/dist/core/session-router.js +53 -13
- package/dist/data/ingest-service.js +91 -9
- package/dist/data/navigation-store.js +5 -5
- package/dist/data/schema.js +122 -2
- package/dist/data/session-store.js +3 -3
- package/dist/debug/index.js +44 -2
- package/dist/gateway/web.js +8 -8
- package/dist/local/client.js +3 -2
- package/dist/plugins/context-files-store.js +30 -0
- package/dist/plugins/context-files.js +52 -1
- package/dist/plugins/registry.js +2 -0
- package/dist/plugins/user-profile-resources.js +22 -0
- package/dist/reliability/store.js +246 -37
- package/dist/session-ui/terminalRows.js +6 -3
- package/dist/sessions/pibo-data-store.js +145 -0
- package/dist/sessions/store.js +42 -0
- package/dist/setup/cli.js +422 -6
- package/dist/setup/installation-profiles.js +464 -0
- package/dist/shared/deterministic-digest.js +94 -0
- package/dist/shared/trace-engine.js +54 -0
- package/dist/shared/trace-event-projection.js +124 -67
- package/dist/shared/trace-history.js +47 -12
- package/dist/shared/trace-live-reducer.js +23 -4
- package/dist/shared/trace-nodes.js +17 -28
- package/dist/shared/trace-order.js +41 -5
- package/dist/shared/trace-page-merge.js +44 -14
- package/dist/shared/trace-patch-nodes.js +2 -0
- package/dist/shared/trace-subagent-links.js +4 -1
- package/dist/shared/trace-tool-identity.js +15 -8
- package/dist/signals/projector.js +28 -6
- package/dist/subagents/tool.js +7 -3
- package/docs/ops/vscode-extension-release.md +9 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
package/dist/gateway/web.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { CHAT_WEB_APP_NAME } from "../apps/chat/web-app.js";
|
|
2
2
|
import { createDefaultPiboPlugins } from "../plugins/builtin.js";
|
|
3
3
|
import { createPiboBetterAuthPlugin } from "../plugins/better-auth.js";
|
|
4
|
-
import { createPiboChatCustomAgentProfilesPlugin } from "../plugins/chat-custom-agents.js";
|
|
5
|
-
import { createPiboChatUserSkillsPlugin } from "../plugins/chat-user-skills.js";
|
|
6
4
|
import { createPiboChatWebPlugin } from "../plugins/chat-web.js";
|
|
7
5
|
import { createPiboChatVscodeWebPlugin } from "../plugins/chat-vscode-web.js";
|
|
8
|
-
import {
|
|
6
|
+
import { createPiboUserProfileResourcePlugins } from "../plugins/user-profile-resources.js";
|
|
9
7
|
import { createPiboPreviewPlugin } from "../previews/plugin.js";
|
|
10
8
|
import { createPiboCronPlugin } from "../cron/plugin.js";
|
|
11
9
|
import { createPiboLoopPlugin } from "../loops/plugin.js";
|
|
@@ -154,13 +152,15 @@ export function createWebPiboPluginRegistry(options = {}) {
|
|
|
154
152
|
landingAppName: CHAT_WEB_APP_NAME,
|
|
155
153
|
}),
|
|
156
154
|
createPiboCronPlugin({ cronStorePath: resolvedOptions.chat?.cronStorePath, dataStorePath: resolvedOptions.chat?.dataStorePath, dataPayloadRootDir: resolvedOptions.chat?.dataPayloadRootDir }),
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
...createPiboUserProfileResourcePlugins({
|
|
156
|
+
userSkills: {
|
|
157
|
+
globalRoot: resolvedOptions.chat?.userSkillGlobalRoot,
|
|
158
|
+
workspaceRoot: resolvedOptions.chat?.userSkillWorkspaceRoot,
|
|
159
|
+
},
|
|
160
|
+
contextFiles: resolvedOptions.contextFiles,
|
|
161
|
+
customAgents: { agentStorePath: resolvedOptions.chat?.agentStorePath },
|
|
160
162
|
}),
|
|
161
|
-
createPiboChatCustomAgentProfilesPlugin({ agentStorePath: resolvedOptions.chat?.agentStorePath }),
|
|
162
163
|
createPiboLoopPlugin({ loopStorePath: resolvedOptions.chat?.ralphStorePath, dataStorePath: resolvedOptions.chat?.dataStorePath, dataPayloadRootDir: resolvedOptions.chat?.dataPayloadRootDir }),
|
|
163
|
-
createPiboContextFilesPlugin(resolvedOptions.contextFiles),
|
|
164
164
|
createPiboPreviewPlugin(),
|
|
165
165
|
createPiboChatWebPlugin(resolvedOptions.chat),
|
|
166
166
|
createPiboChatVscodeWebPlugin(),
|
package/dist/local/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { PiboSessionRouter } from "../core/session-router.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault } from "../plugins/builtin.js";
|
|
4
|
+
import { createDefaultPiboUserProfileRegistry } from "../plugins/user-profile-resources.js";
|
|
4
5
|
import { getDefaultPiboWorkspace } from "../core/workspace.js";
|
|
5
6
|
import { InMemoryPiboSessionStore, } from "../sessions/store.js";
|
|
6
7
|
export const LOCAL_TUI_CHANNEL_NAME = "local-tui";
|
|
@@ -52,7 +53,7 @@ export class LocalRoutedTuiClient {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
export function createLocalRoutedTuiClient(options = {}) {
|
|
55
|
-
const registry = options.pluginRegistry ??
|
|
56
|
+
const registry = options.pluginRegistry ?? createDefaultPiboUserProfileRegistry();
|
|
56
57
|
const profileName = resolvePiboProfileNameFromRegistryOrDefault(registry, options.profile);
|
|
57
58
|
const profile = createPiboProfileFromRegistryOrDefault(registry, profileName);
|
|
58
59
|
const sessionName = options.sessionName ?? "default";
|
|
@@ -101,6 +101,36 @@ function revisionRowToRecord(row) {
|
|
|
101
101
|
...(row.actor_id ? { actorId: row.actor_id } : {}),
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
+
export function readContextFileCatalog(path) {
|
|
105
|
+
const resolvedPath = resolve(path);
|
|
106
|
+
if (!existsSync(resolvedPath))
|
|
107
|
+
return [];
|
|
108
|
+
const db = new DatabaseSync(resolvedPath, { readOnly: true });
|
|
109
|
+
try {
|
|
110
|
+
const table = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'context_files'").get();
|
|
111
|
+
if (!table)
|
|
112
|
+
return [];
|
|
113
|
+
const columns = new Set(db.prepare("PRAGMA table_info(context_files)").all().map((column) => column.name));
|
|
114
|
+
const required = ["key", "label", "managed_path", "scope", "agent_profile_name"];
|
|
115
|
+
if (!required.every((column) => columns.has(column)))
|
|
116
|
+
return [];
|
|
117
|
+
const rows = db.prepare(`
|
|
118
|
+
SELECT key, label, managed_path, scope, agent_profile_name
|
|
119
|
+
FROM context_files
|
|
120
|
+
ORDER BY updated_at DESC
|
|
121
|
+
`).all();
|
|
122
|
+
return rows.map((row) => ({
|
|
123
|
+
key: row.key,
|
|
124
|
+
label: row.label,
|
|
125
|
+
managedPath: row.managed_path,
|
|
126
|
+
scope: row.scope,
|
|
127
|
+
...(row.agent_profile_name ? { agentProfileName: row.agent_profile_name } : {}),
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
db.close();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
104
134
|
export class ContextFileMetadataStore {
|
|
105
135
|
legacyStorePath;
|
|
106
136
|
db;
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { readFallbackPidFile, readPidFile } from "../gateway/pidfile.js";
|
|
7
7
|
import { PiboWebHttpError, readJsonBody, responseHtml, responseJson } from "../web/http.js";
|
|
8
8
|
import { definePiboPlugin } from "./registry.js";
|
|
9
|
-
import { buildContextFileDiff, ContextFileMetadataStore, hashContextFileContent, } from "./context-files-store.js";
|
|
9
|
+
import { buildContextFileDiff, ContextFileMetadataStore, hashContextFileContent, readContextFileCatalog, } from "./context-files-store.js";
|
|
10
10
|
export const CONTEXT_FILES_APP_NAME = "pibo.context-files";
|
|
11
11
|
export const CONTEXT_FILES_MOUNT_PATH = "/apps/context-files";
|
|
12
12
|
export const CONTEXT_FILES_API_PREFIX = "/api/context-files";
|
|
@@ -1028,6 +1028,57 @@ function createContextFilesWebApp(service) {
|
|
|
1028
1028
|
},
|
|
1029
1029
|
};
|
|
1030
1030
|
}
|
|
1031
|
+
export function createPiboContextFilesCatalogPlugin(options = {}) {
|
|
1032
|
+
const paths = resolveContextFilesPaths(options);
|
|
1033
|
+
return definePiboPlugin({
|
|
1034
|
+
id: "pibo.context-files-catalog",
|
|
1035
|
+
name: "Pibo Context Files Catalog",
|
|
1036
|
+
register(api) {
|
|
1037
|
+
const registeredPaths = new Set();
|
|
1038
|
+
const registeredKeys = new Set();
|
|
1039
|
+
try {
|
|
1040
|
+
for (const file of readContextFileCatalog(paths.metadataPath)) {
|
|
1041
|
+
api.upsertContextFile(profileForManaged(file));
|
|
1042
|
+
registeredPaths.add(resolve(file.managedPath));
|
|
1043
|
+
registeredKeys.add(file.key);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
catch (error) {
|
|
1047
|
+
console.warn(`[pibo] Skipping managed context-file catalog registration: ${error instanceof Error ? error.message : String(error)}`);
|
|
1048
|
+
}
|
|
1049
|
+
let entries = [];
|
|
1050
|
+
try {
|
|
1051
|
+
entries = readdirSync(paths.globalDir, { withFileTypes: true });
|
|
1052
|
+
}
|
|
1053
|
+
catch (error) {
|
|
1054
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
1055
|
+
if (code !== "ENOENT") {
|
|
1056
|
+
console.warn(`[pibo] Skipping global context-file discovery: ${error instanceof Error ? error.message : String(error)}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const usedKeys = new Set(registeredKeys);
|
|
1060
|
+
for (const entry of entries) {
|
|
1061
|
+
if (!entry.isFile())
|
|
1062
|
+
continue;
|
|
1063
|
+
const extension = extname(entry.name).toLowerCase();
|
|
1064
|
+
if (extension !== ".md" && extension !== ".markdown")
|
|
1065
|
+
continue;
|
|
1066
|
+
const path = resolve(paths.globalDir, entry.name);
|
|
1067
|
+
if (registeredPaths.has(path))
|
|
1068
|
+
continue;
|
|
1069
|
+
const key = uniqueKey(`ctx:${slugSegment(entry.name.slice(0, -extension.length) || entry.name)}`, usedKeys);
|
|
1070
|
+
usedKeys.add(key);
|
|
1071
|
+
api.upsertContextFile({
|
|
1072
|
+
key,
|
|
1073
|
+
label: labelFromManagedPath(entry.name),
|
|
1074
|
+
path,
|
|
1075
|
+
source: "managed",
|
|
1076
|
+
scope: "global",
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
},
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1031
1082
|
export function createPiboContextFilesPlugin(options = {}) {
|
|
1032
1083
|
const paths = resolveContextFilesPaths(options);
|
|
1033
1084
|
return definePiboPlugin({
|
package/dist/plugins/registry.js
CHANGED
|
@@ -521,11 +521,13 @@ export class PiboPluginRegistry {
|
|
|
521
521
|
nativeTools: sessionContext.tools.filter((tool) => tool.enabled !== false).map((tool) => tool.name),
|
|
522
522
|
skills: sessionContext.skills.filter((skill) => skill.enabled !== false).map((skill) => skill.name),
|
|
523
523
|
contextFiles: sessionContext.contextFiles.filter((contextFile) => contextFile.enabled !== false).map(contextFileKey),
|
|
524
|
+
diagnostics: sessionContext.diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
524
525
|
subagents: sessionContext.subagents.filter((subagent) => subagent.enabled !== false),
|
|
525
526
|
mcpServers: [...sessionContext.mcpServers],
|
|
526
527
|
piPackages: sessionContext.piPackages.filter((pkg) => pkg.enabled !== false).map((pkg) => pkg.id),
|
|
527
528
|
model: sessionContext.model ? { ...sessionContext.model } : undefined,
|
|
528
529
|
mainModel: sessionContext.mainModel ? { ...sessionContext.mainModel } : undefined,
|
|
530
|
+
mainModelFallbacks: sessionContext.mainModelFallbacks.map((model) => ({ ...model })),
|
|
529
531
|
subagentModel: sessionContext.subagentModel ? { ...sessionContext.subagentModel } : undefined,
|
|
530
532
|
thinkingLevel: sessionContext.thinkingLevel,
|
|
531
533
|
mainThinkingLevel: sessionContext.mainThinkingLevel,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createPiboChatCustomAgentProfilesPlugin, } from "./chat-custom-agents.js";
|
|
2
|
+
import { createPiboChatUserSkillsPlugin } from "./chat-user-skills.js";
|
|
3
|
+
import { createPiboContextFilesCatalogPlugin, createPiboContextFilesPlugin, } from "./context-files.js";
|
|
4
|
+
import { createDefaultPiboPluginRegistry } from "./builtin.js";
|
|
5
|
+
export function createPiboUserProfileResourcePlugins(options = {}) {
|
|
6
|
+
return [
|
|
7
|
+
createPiboChatUserSkillsPlugin(options.userSkills),
|
|
8
|
+
options.contextFilesMode === "catalog"
|
|
9
|
+
? createPiboContextFilesCatalogPlugin(options.contextFiles)
|
|
10
|
+
: createPiboContextFilesPlugin(options.contextFiles),
|
|
11
|
+
createPiboChatCustomAgentProfilesPlugin(options.customAgents),
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
export function createDefaultPiboUserProfileRegistry(options = {}) {
|
|
15
|
+
const registry = createDefaultPiboPluginRegistry();
|
|
16
|
+
registerPiboUserProfileResources(registry, { contextFilesMode: "catalog", ...options });
|
|
17
|
+
return registry;
|
|
18
|
+
}
|
|
19
|
+
export function registerPiboUserProfileResources(registry, options = {}) {
|
|
20
|
+
for (const plugin of createPiboUserProfileResourcePlugins(options))
|
|
21
|
+
registry.registerPlugin(plugin);
|
|
22
|
+
}
|
|
@@ -36,6 +36,11 @@ function ensurePiboRunColumn(db, name, definition) {
|
|
|
36
36
|
if (!columns.has(name))
|
|
37
37
|
db.exec(`ALTER TABLE pibo_runs ADD COLUMN ${name} ${definition}`);
|
|
38
38
|
}
|
|
39
|
+
function ensurePiboJobColumn(db, name, definition) {
|
|
40
|
+
const columns = sqliteTableColumns(db, "pibo_jobs");
|
|
41
|
+
if (!columns.has(name))
|
|
42
|
+
db.exec(`ALTER TABLE pibo_jobs ADD COLUMN ${name} ${definition}`);
|
|
43
|
+
}
|
|
39
44
|
export class PiboReliabilityStore {
|
|
40
45
|
db;
|
|
41
46
|
appendEventStatement;
|
|
@@ -74,6 +79,13 @@ export class PiboReliabilityStore {
|
|
|
74
79
|
CREATE INDEX IF NOT EXISTS idx_pibo_event_stream_topic_stream
|
|
75
80
|
ON pibo_event_stream(topic, stream_id);
|
|
76
81
|
|
|
82
|
+
CREATE TABLE IF NOT EXISTS pibo_delivery_receipts (
|
|
83
|
+
delivery_id TEXT NOT NULL,
|
|
84
|
+
projection TEXT NOT NULL,
|
|
85
|
+
delivered_at TEXT NOT NULL,
|
|
86
|
+
PRIMARY KEY(delivery_id, projection)
|
|
87
|
+
);
|
|
88
|
+
|
|
77
89
|
CREATE TABLE IF NOT EXISTS pibo_event_consumers (
|
|
78
90
|
consumer TEXT NOT NULL,
|
|
79
91
|
topic TEXT NOT NULL,
|
|
@@ -91,6 +103,7 @@ export class PiboReliabilityStore {
|
|
|
91
103
|
priority INTEGER NOT NULL DEFAULT 0,
|
|
92
104
|
worker_id TEXT,
|
|
93
105
|
claim_expires_at TEXT,
|
|
106
|
+
claim_token INTEGER NOT NULL DEFAULT 0,
|
|
94
107
|
attempts INTEGER NOT NULL DEFAULT 0,
|
|
95
108
|
max_attempts INTEGER NOT NULL DEFAULT 1,
|
|
96
109
|
idempotency_key TEXT,
|
|
@@ -157,6 +170,7 @@ export class PiboReliabilityStore {
|
|
|
157
170
|
CREATE INDEX IF NOT EXISTS idx_pibo_runs_status
|
|
158
171
|
ON pibo_runs(status);
|
|
159
172
|
`);
|
|
173
|
+
ensurePiboJobColumn(this.db, "claim_token", "INTEGER NOT NULL DEFAULT 0");
|
|
160
174
|
ensurePiboRunColumn(this.db, "timeout_ms", "INTEGER");
|
|
161
175
|
ensurePiboRunColumn(this.db, "timeout_at", "TEXT");
|
|
162
176
|
ensurePiboRunColumn(this.db, "timeout_phase", "TEXT");
|
|
@@ -189,6 +203,21 @@ export class PiboReliabilityStore {
|
|
|
189
203
|
throw error;
|
|
190
204
|
}
|
|
191
205
|
}
|
|
206
|
+
hasDeliveryReceipt(deliveryId, projection) {
|
|
207
|
+
return this.db
|
|
208
|
+
.prepare("SELECT 1 AS found FROM pibo_delivery_receipts WHERE delivery_id = ? AND projection = ?")
|
|
209
|
+
.get(deliveryId, projection) !== undefined;
|
|
210
|
+
}
|
|
211
|
+
recordDeliveryReceipt(deliveryId, projection, deliveredAt = now()) {
|
|
212
|
+
this.db.prepare(`
|
|
213
|
+
INSERT OR IGNORE INTO pibo_delivery_receipts (delivery_id, projection, delivered_at)
|
|
214
|
+
VALUES (?, ?, ?)
|
|
215
|
+
`).run(deliveryId, projection, deliveredAt);
|
|
216
|
+
const row = this.db
|
|
217
|
+
.prepare("SELECT delivery_id, projection, delivered_at FROM pibo_delivery_receipts WHERE delivery_id = ? AND projection = ?")
|
|
218
|
+
.get(deliveryId, projection);
|
|
219
|
+
return { deliveryId: row.delivery_id, projection: row.projection, deliveredAt: row.delivered_at };
|
|
220
|
+
}
|
|
192
221
|
list(input = {}) {
|
|
193
222
|
const clauses = [];
|
|
194
223
|
const values = [];
|
|
@@ -339,6 +368,7 @@ export class PiboReliabilityStore {
|
|
|
339
368
|
SET state = 'running',
|
|
340
369
|
worker_id = ?,
|
|
341
370
|
claim_expires_at = ?,
|
|
371
|
+
claim_token = claim_token + 1,
|
|
342
372
|
attempts = attempts + 1,
|
|
343
373
|
updated_at = ?
|
|
344
374
|
WHERE job_id IN (
|
|
@@ -361,6 +391,7 @@ export class PiboReliabilityStore {
|
|
|
361
391
|
SET state = 'running',
|
|
362
392
|
worker_id = ?,
|
|
363
393
|
claim_expires_at = ?,
|
|
394
|
+
claim_token = claim_token + 1,
|
|
364
395
|
attempts = attempts + 1,
|
|
365
396
|
updated_at = ?
|
|
366
397
|
WHERE job_id = ?
|
|
@@ -372,7 +403,50 @@ export class PiboReliabilityStore {
|
|
|
372
403
|
.get(workerId, claimExpiresAt, timestamp, jobId, timestamp, timestamp);
|
|
373
404
|
return row ? jobFromRow(row) : undefined;
|
|
374
405
|
}
|
|
375
|
-
|
|
406
|
+
claimRecoverableJob(jobId, workerId, visibilityTimeoutMs = 30000, forcePending = false) {
|
|
407
|
+
const timestamp = now();
|
|
408
|
+
const claimExpiresAt = new Date(Date.now() + visibilityTimeoutMs).toISOString();
|
|
409
|
+
const row = this.db.prepare(`
|
|
410
|
+
UPDATE pibo_jobs
|
|
411
|
+
SET state = 'running',
|
|
412
|
+
worker_id = ?,
|
|
413
|
+
claim_expires_at = ?,
|
|
414
|
+
claim_token = claim_token + 1,
|
|
415
|
+
attempts = attempts + 1,
|
|
416
|
+
updated_at = ?
|
|
417
|
+
WHERE job_id = ?
|
|
418
|
+
AND (
|
|
419
|
+
(state = 'pending' AND (? = 1 OR run_at <= ?))
|
|
420
|
+
OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?)
|
|
421
|
+
)
|
|
422
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
423
|
+
RETURNING *
|
|
424
|
+
`).get(workerId, claimExpiresAt, timestamp, jobId, forcePending ? 1 : 0, timestamp, timestamp, timestamp);
|
|
425
|
+
return row ? jobFromRow(row) : undefined;
|
|
426
|
+
}
|
|
427
|
+
updateJobPayload(jobId, workerId, payload, claimToken) {
|
|
428
|
+
const result = this.db.prepare(`
|
|
429
|
+
UPDATE pibo_jobs
|
|
430
|
+
SET payload_json = ?, updated_at = ?
|
|
431
|
+
WHERE job_id = ? AND state = 'running' AND worker_id = ?
|
|
432
|
+
AND (? IS NULL OR claim_token = ?)
|
|
433
|
+
AND claim_expires_at IS NOT NULL AND claim_expires_at > ?
|
|
434
|
+
`).run(json(payload), now(), jobId, workerId, claimToken ?? null, claimToken ?? null, now());
|
|
435
|
+
return Number(result.changes ?? 0) > 0;
|
|
436
|
+
}
|
|
437
|
+
releaseJob(jobId, workerId, delayMs = 0, claimToken) {
|
|
438
|
+
const timestamp = now();
|
|
439
|
+
const runAt = new Date(Date.parse(timestamp) + Math.max(0, delayMs)).toISOString();
|
|
440
|
+
const result = this.db.prepare(`
|
|
441
|
+
UPDATE pibo_jobs
|
|
442
|
+
SET state = 'pending', worker_id = NULL, claim_expires_at = NULL, run_at = ?, updated_at = ?
|
|
443
|
+
WHERE job_id = ? AND state = 'running' AND worker_id = ?
|
|
444
|
+
AND (? IS NULL OR claim_token = ?)
|
|
445
|
+
AND claim_expires_at IS NOT NULL AND claim_expires_at > ?
|
|
446
|
+
`).run(runAt, timestamp, jobId, workerId, claimToken ?? null, claimToken ?? null, timestamp);
|
|
447
|
+
return Number(result.changes ?? 0) > 0;
|
|
448
|
+
}
|
|
449
|
+
ack(jobId, workerId, claimToken) {
|
|
376
450
|
const timestamp = now();
|
|
377
451
|
const result = this.db
|
|
378
452
|
.prepare(`
|
|
@@ -380,47 +454,53 @@ export class PiboReliabilityStore {
|
|
|
380
454
|
WHERE job_id = ?
|
|
381
455
|
AND state = 'running'
|
|
382
456
|
AND worker_id = ?
|
|
457
|
+
AND (? IS NULL OR claim_token = ?)
|
|
383
458
|
AND claim_expires_at IS NOT NULL
|
|
384
459
|
AND claim_expires_at > ?
|
|
385
460
|
`)
|
|
386
|
-
.run(jobId, workerId, timestamp);
|
|
461
|
+
.run(jobId, workerId, claimToken ?? null, claimToken ?? null, timestamp);
|
|
387
462
|
return Number(result.changes ?? 0) > 0;
|
|
388
463
|
}
|
|
389
464
|
retry(jobId, workerId, input = {}) {
|
|
390
465
|
const timestamp = now();
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
466
|
+
return this.inImmediateTransaction(() => {
|
|
467
|
+
const row = this.getLiveWorkerJob(jobId, workerId, timestamp, input.claimToken);
|
|
468
|
+
if (!row)
|
|
469
|
+
return false;
|
|
470
|
+
const job = jobFromRow(row);
|
|
471
|
+
if (job.attempts >= job.maxAttempts) {
|
|
472
|
+
this.moveJobToDead(row, input.error ?? "Job exhausted retry attempts.", "max_attempts", timestamp);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
const delayMs = input.delayMs ?? retryDelayMs(job.attempts, input);
|
|
476
|
+
const runAt = new Date(asDate(input.now).getTime() + delayMs).toISOString();
|
|
477
|
+
const result = this.db
|
|
478
|
+
.prepare(`
|
|
479
|
+
UPDATE pibo_jobs
|
|
480
|
+
SET state = 'pending',
|
|
481
|
+
worker_id = NULL,
|
|
482
|
+
claim_expires_at = NULL,
|
|
483
|
+
run_at = ?,
|
|
484
|
+
updated_at = ?,
|
|
485
|
+
last_error = ?
|
|
486
|
+
WHERE job_id = ? AND state = 'running' AND worker_id = ?
|
|
487
|
+
AND (? IS NULL OR claim_token = ?)
|
|
488
|
+
`)
|
|
489
|
+
.run(runAt, timestamp, input.error ?? null, jobId, workerId, input.claimToken ?? null, input.claimToken ?? null);
|
|
490
|
+
return Number(result.changes ?? 0) > 0;
|
|
491
|
+
});
|
|
414
492
|
}
|
|
415
|
-
fail(jobId, workerId, error) {
|
|
493
|
+
fail(jobId, workerId, error, claimToken) {
|
|
416
494
|
const timestamp = now();
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
495
|
+
return this.inImmediateTransaction(() => {
|
|
496
|
+
const row = this.getLiveWorkerJob(jobId, workerId, timestamp, claimToken);
|
|
497
|
+
if (!row)
|
|
498
|
+
return false;
|
|
499
|
+
this.moveJobToDead(row, error, "failed", timestamp);
|
|
500
|
+
return true;
|
|
501
|
+
});
|
|
422
502
|
}
|
|
423
|
-
heartbeat(jobId, workerId, extendMs) {
|
|
503
|
+
heartbeat(jobId, workerId, extendMs, claimToken) {
|
|
424
504
|
const timestamp = now();
|
|
425
505
|
const claimExpiresAt = new Date(Date.now() + Math.max(1, extendMs)).toISOString();
|
|
426
506
|
const result = this.db
|
|
@@ -430,10 +510,11 @@ export class PiboReliabilityStore {
|
|
|
430
510
|
WHERE job_id = ?
|
|
431
511
|
AND state = 'running'
|
|
432
512
|
AND worker_id = ?
|
|
513
|
+
AND (? IS NULL OR claim_token = ?)
|
|
433
514
|
AND claim_expires_at IS NOT NULL
|
|
434
515
|
AND claim_expires_at > ?
|
|
435
516
|
`)
|
|
436
|
-
.run(claimExpiresAt, timestamp, jobId, workerId, timestamp);
|
|
517
|
+
.run(claimExpiresAt, timestamp, jobId, workerId, claimToken ?? null, claimToken ?? null, timestamp);
|
|
437
518
|
return Number(result.changes ?? 0) > 0;
|
|
438
519
|
}
|
|
439
520
|
listJobs(input = {}) {
|
|
@@ -447,11 +528,56 @@ export class PiboReliabilityStore {
|
|
|
447
528
|
clauses.push("state = ?");
|
|
448
529
|
values.push(input.state);
|
|
449
530
|
}
|
|
531
|
+
if (input.excludeJobIds?.length) {
|
|
532
|
+
clauses.push(`job_id NOT IN (${input.excludeJobIds.map(() => "?").join(", ")})`);
|
|
533
|
+
values.push(...input.excludeJobIds);
|
|
534
|
+
}
|
|
450
535
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
451
536
|
const rows = this.db
|
|
452
537
|
.prepare(`SELECT * FROM pibo_jobs ${where} ORDER BY priority DESC, run_at ASC, created_at ASC LIMIT ?`)
|
|
453
538
|
.all(...values, clampLimit(input.limit, 100));
|
|
454
|
-
|
|
539
|
+
const jobs = [];
|
|
540
|
+
for (const row of rows) {
|
|
541
|
+
try {
|
|
542
|
+
jobs.push(jobFromRow(row));
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
this.quarantineJobRow(row, "payload_malformed");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return jobs;
|
|
549
|
+
}
|
|
550
|
+
listRecoverableJobs(input = {}) {
|
|
551
|
+
const timestamp = now();
|
|
552
|
+
const clauses = [
|
|
553
|
+
"((state = 'pending' AND (? = 1 OR run_at <= ?)) OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?))",
|
|
554
|
+
"(expires_at IS NULL OR expires_at > ?)",
|
|
555
|
+
];
|
|
556
|
+
const values = [input.includeDeferredPending ? 1 : 0, timestamp, timestamp, timestamp];
|
|
557
|
+
if (input.queue) {
|
|
558
|
+
clauses.push("queue = ?");
|
|
559
|
+
values.push(input.queue);
|
|
560
|
+
}
|
|
561
|
+
if (input.excludeJobIds?.length) {
|
|
562
|
+
clauses.push(`job_id NOT IN (${input.excludeJobIds.map(() => "?").join(", ")})`);
|
|
563
|
+
values.push(...input.excludeJobIds);
|
|
564
|
+
}
|
|
565
|
+
const rows = this.db.prepare(`
|
|
566
|
+
SELECT * FROM pibo_jobs
|
|
567
|
+
WHERE ${clauses.join(" AND ")}
|
|
568
|
+
ORDER BY priority DESC, run_at ASC, created_at ASC
|
|
569
|
+
LIMIT ?
|
|
570
|
+
`).all(...values, clampLimit(input.limit, 100));
|
|
571
|
+
const jobs = [];
|
|
572
|
+
for (const row of rows) {
|
|
573
|
+
try {
|
|
574
|
+
jobs.push(jobFromRow(row));
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
this.quarantineJobRow(row, "payload_malformed");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return jobs;
|
|
455
581
|
}
|
|
456
582
|
listDead(input = {}) {
|
|
457
583
|
const clauses = [];
|
|
@@ -466,6 +592,46 @@ export class PiboReliabilityStore {
|
|
|
466
592
|
.all(...values, clampLimit(input.limit, 100));
|
|
467
593
|
return rows.map(deadJobFromRow);
|
|
468
594
|
}
|
|
595
|
+
hasLiveJob(jobId) {
|
|
596
|
+
return this.db.prepare("SELECT 1 AS found FROM pibo_jobs WHERE job_id = ?").get(jobId) !== undefined;
|
|
597
|
+
}
|
|
598
|
+
hasJobs(queue) {
|
|
599
|
+
return queue
|
|
600
|
+
? this.db.prepare("SELECT 1 AS found FROM pibo_jobs WHERE queue = ? LIMIT 1").get(queue) !== undefined
|
|
601
|
+
: this.db.prepare("SELECT 1 AS found FROM pibo_jobs LIMIT 1").get() !== undefined;
|
|
602
|
+
}
|
|
603
|
+
hasRecoverableJobs(queue, includeDeferredPending = false) {
|
|
604
|
+
const timestamp = now();
|
|
605
|
+
return this.db.prepare(`
|
|
606
|
+
SELECT 1 AS found FROM pibo_jobs
|
|
607
|
+
WHERE queue = ?
|
|
608
|
+
AND (
|
|
609
|
+
(state = 'pending' AND (? = 1 OR run_at <= ?))
|
|
610
|
+
OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?)
|
|
611
|
+
)
|
|
612
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
613
|
+
LIMIT 1
|
|
614
|
+
`).get(queue, includeDeferredPending ? 1 : 0, timestamp, timestamp, timestamp) !== undefined;
|
|
615
|
+
}
|
|
616
|
+
quarantineJob(jobId, reason, correlation = {}) {
|
|
617
|
+
const row = this.db.prepare("SELECT * FROM pibo_jobs WHERE job_id = ?").get(jobId);
|
|
618
|
+
if (!row)
|
|
619
|
+
return false;
|
|
620
|
+
this.quarantineJobRow(row, reason, correlation);
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
deadLetter(input) {
|
|
624
|
+
const timestamp = now();
|
|
625
|
+
const jobId = input.jobId ?? `job_${randomUUID()}`;
|
|
626
|
+
this.db.prepare(`
|
|
627
|
+
INSERT OR REPLACE INTO pibo_dead_jobs (
|
|
628
|
+
job_id, queue, payload_json, attempts, max_attempts, idempotency_key, created_at,
|
|
629
|
+
updated_at, expires_at, last_error, dead_at, dead_reason
|
|
630
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)
|
|
631
|
+
`).run(jobId, input.queue, json(input.payload ?? null), Math.max(0, input.attempts ?? 0), Math.max(1, input.maxAttempts ?? 1), input.idempotencyKey ?? null, timestamp, timestamp, input.error ?? input.reason, timestamp, input.reason);
|
|
632
|
+
const row = this.db.prepare("SELECT * FROM pibo_dead_jobs WHERE job_id = ?").get(jobId);
|
|
633
|
+
return deadJobFromRow(row);
|
|
634
|
+
}
|
|
469
635
|
requeueDead(jobId, input = {}) {
|
|
470
636
|
const dead = this.db.prepare("SELECT * FROM pibo_dead_jobs WHERE job_id = ?").get(jobId);
|
|
471
637
|
if (!dead)
|
|
@@ -659,17 +825,18 @@ export class PiboReliabilityStore {
|
|
|
659
825
|
.get(queue, idempotencyKey);
|
|
660
826
|
return row ? jobFromRow(row) : undefined;
|
|
661
827
|
}
|
|
662
|
-
getLiveWorkerJob(jobId, workerId, timestamp) {
|
|
828
|
+
getLiveWorkerJob(jobId, workerId, timestamp, claimToken) {
|
|
663
829
|
return this.db
|
|
664
830
|
.prepare(`
|
|
665
831
|
SELECT * FROM pibo_jobs
|
|
666
832
|
WHERE job_id = ?
|
|
667
833
|
AND state = 'running'
|
|
668
834
|
AND worker_id = ?
|
|
835
|
+
AND (? IS NULL OR claim_token = ?)
|
|
669
836
|
AND claim_expires_at IS NOT NULL
|
|
670
837
|
AND claim_expires_at > ?
|
|
671
838
|
`)
|
|
672
|
-
.get(jobId, workerId, timestamp);
|
|
839
|
+
.get(jobId, workerId, claimToken ?? null, claimToken ?? null, timestamp);
|
|
673
840
|
}
|
|
674
841
|
moveExpiredJobs(timestamp) {
|
|
675
842
|
const expired = this.db.prepare("SELECT * FROM pibo_jobs WHERE expires_at IS NOT NULL AND expires_at <= ?").all(timestamp);
|
|
@@ -718,6 +885,39 @@ export class PiboReliabilityStore {
|
|
|
718
885
|
.run(row.job_id, row.queue, row.payload_json, row.attempts, row.max_attempts, row.idempotency_key, row.created_at, timestamp, row.expires_at, error, timestamp, reason);
|
|
719
886
|
this.db.prepare("DELETE FROM pibo_jobs WHERE job_id = ?").run(row.job_id);
|
|
720
887
|
}
|
|
888
|
+
quarantineJobRow(row, reason, correlation = {}) {
|
|
889
|
+
this.inImmediateTransaction(() => {
|
|
890
|
+
const current = this.db.prepare("SELECT * FROM pibo_jobs WHERE job_id = ?").get(row.job_id);
|
|
891
|
+
if (!current)
|
|
892
|
+
return;
|
|
893
|
+
const payload = {
|
|
894
|
+
version: 1,
|
|
895
|
+
key: correlation.key ?? current.job_id,
|
|
896
|
+
...(correlation.piboSessionId ? { piboSessionId: correlation.piboSessionId } : {}),
|
|
897
|
+
...(correlation.eventId ? { eventId: correlation.eventId } : {}),
|
|
898
|
+
state: { phase: "quarantined", reasonCode: reason },
|
|
899
|
+
};
|
|
900
|
+
const sanitized = {
|
|
901
|
+
...current,
|
|
902
|
+
payload_json: json(payload),
|
|
903
|
+
idempotency_key: correlation.key ?? null,
|
|
904
|
+
last_error: reason,
|
|
905
|
+
};
|
|
906
|
+
this.moveJobToDead(sanitized, reason, reason, now());
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
inImmediateTransaction(operation) {
|
|
910
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
911
|
+
try {
|
|
912
|
+
const result = operation();
|
|
913
|
+
this.db.exec("COMMIT");
|
|
914
|
+
return result;
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
this.db.exec("ROLLBACK");
|
|
918
|
+
throw error;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
721
921
|
requireRun(runId) {
|
|
722
922
|
const run = this.getRun(runId);
|
|
723
923
|
if (!run)
|
|
@@ -750,6 +950,7 @@ function jobFromRow(row) {
|
|
|
750
950
|
priority: row.priority,
|
|
751
951
|
workerId: row.worker_id ?? undefined,
|
|
752
952
|
claimExpiresAt: row.claim_expires_at ?? undefined,
|
|
953
|
+
claimToken: row.claim_token,
|
|
753
954
|
attempts: row.attempts,
|
|
754
955
|
maxAttempts: row.max_attempts,
|
|
755
956
|
idempotencyKey: row.idempotency_key ?? undefined,
|
|
@@ -763,7 +964,7 @@ function deadJobFromRow(row) {
|
|
|
763
964
|
return {
|
|
764
965
|
jobId: row.job_id,
|
|
765
966
|
queue: row.queue,
|
|
766
|
-
payload:
|
|
967
|
+
payload: safeDeadPayload(row.payload_json, row.dead_reason, row.job_id),
|
|
767
968
|
attempts: row.attempts,
|
|
768
969
|
maxAttempts: row.max_attempts,
|
|
769
970
|
idempotencyKey: row.idempotency_key ?? undefined,
|
|
@@ -775,6 +976,14 @@ function deadJobFromRow(row) {
|
|
|
775
976
|
deadReason: row.dead_reason,
|
|
776
977
|
};
|
|
777
978
|
}
|
|
979
|
+
function safeDeadPayload(value, reason, jobId) {
|
|
980
|
+
try {
|
|
981
|
+
return parseJson(value);
|
|
982
|
+
}
|
|
983
|
+
catch {
|
|
984
|
+
return { version: 1, key: jobId, state: { phase: "quarantined", reasonCode: reason } };
|
|
985
|
+
}
|
|
986
|
+
}
|
|
778
987
|
function runFromRow(row) {
|
|
779
988
|
const output = {
|
|
780
989
|
runId: row.run_id,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compareTraceNodes } from "../shared/trace-nodes.js";
|
|
2
|
+
import { parseTraceToolNodeIdentity } from "../shared/trace-tool-identity.js";
|
|
2
3
|
import { terminalTextValue } from "./terminalValue.js";
|
|
3
4
|
export const MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS = 20;
|
|
4
5
|
export const COMPACT_TERMINAL_OUTPUT_PREVIEW_LINES = 5;
|
|
@@ -168,9 +169,11 @@ function createRowCandidate(node, turnId) {
|
|
|
168
169
|
}
|
|
169
170
|
export function compactTerminalRowIdentity(node) {
|
|
170
171
|
if (node.type === "tool.call" || node.type === "tool.result" || node.type === "agent.delegation") {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
if (node.toolCallId) {
|
|
173
|
+
return parseTraceToolNodeIdentity(node.id)?.qualifier
|
|
174
|
+
? `terminal:${node.id}`
|
|
175
|
+
: `terminal:tool:${node.toolCallId}`;
|
|
176
|
+
}
|
|
174
177
|
if (node.type === "agent.delegation" && node.linkedPiboSessionId) {
|
|
175
178
|
return `terminal:delegation:${node.linkedPiboSessionId}`;
|
|
176
179
|
}
|