@pasko70/pibo 1.9.11 → 1.10.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/apps/chat/agent-profiles.js +2 -2
- package/dist/apps/chat/agent-store.js +16 -3
- package/dist/apps/chat/chat-request-normalizers.js +10 -0
- package/dist/apps/chat/data/timeline-query-service.js +11 -0
- package/dist/apps/chat/loop-api.js +176 -0
- package/dist/apps/chat/trace.js +2 -0
- package/dist/apps/chat/web-app.js +13 -6
- package/dist/apps/chat/workflow-manual-trigger-runtime.js +149 -46
- package/dist/apps/chat-ui/assets/{dist-yCYNNb5d.js → dist-BwKObYnX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BCB6zezO.js → dist-CKtT8YGm.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CnVsqwSG.js → dist-CS7wdk0Z.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-HqTN67dc.js → dist-D-cxLQO1.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BNMu92bb.js → dist-DUlaXAk7.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CzE6k3F3.js → dist-DlATLa-U.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dq4GxJi3.js → dist-GdEM8UW1.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BZ2eTC4f.js → dist-LHRs1Nhr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D811wJeV.js → dist-Y-AA2omI.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-WyXdYl-w.js → dist-nOLTkZrJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BHa-kcGl.js → dist-wE9nop9V.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-DwHJfmiF.js → index-8W_yMHQI.js} +6 -6
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/cli.js +23 -6
- package/dist/core/routed-session.js +30 -1
- package/dist/core/runtime.js +6 -1
- package/dist/core/session-router.js +4 -2
- package/dist/data/ingest-service.js +7 -0
- package/dist/data/message-store.js +11 -0
- package/dist/data/schema.js +2 -0
- package/dist/gateway/server.js +4 -2
- package/dist/gateway/web.js +2 -2
- package/dist/loops/channel.js +8 -0
- package/dist/loops/cli.js +208 -0
- package/dist/loops/plugin.js +16 -0
- package/dist/loops/prompts.js +83 -0
- package/dist/loops/service.js +357 -0
- package/dist/loops/stopping.js +170 -0
- package/dist/loops/store.js +531 -0
- package/dist/loops/templates.js +232 -0
- package/dist/loops/tools.js +167 -0
- package/dist/loops/types.js +1 -0
- package/dist/plugins/builtin.js +8 -0
- package/dist/plugins/registry.js +23 -12
- package/dist/resources/lifecycle.js +4 -6
- package/dist/resources/reaper-state.js +30 -3
- package/dist/resources/reaper.js +43 -15
- package/dist/shared/trace-engine.js +3 -2
- package/dist/shared/trace-event-projection.js +26 -0
- package/dist/tools/guides.js +51 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/registry.js +21 -3
- package/package.json +5 -2
- package/skills/builtin/loop/SKILL.md +69 -0
- package/skills/builtin/ralph-loop/SKILL.md +4 -2
|
@@ -3,7 +3,7 @@ export function createCustomAgentProfileDefinition(agent, options = {}) {
|
|
|
3
3
|
const shouldWarnMissingReferences = options.missingReferenceMode !== "silent";
|
|
4
4
|
return {
|
|
5
5
|
name: agent.profileName,
|
|
6
|
-
aliases: uniqueAliases([agent.id, `custom-agent:${agent.id}`, ...agent.profileAliases], agent.profileName),
|
|
6
|
+
aliases: uniqueAliases([agent.id, `custom-agent:${agent.id}`, ...(agent.profileAliases ?? [])], agent.profileName),
|
|
7
7
|
description: agent.description || agent.displayName,
|
|
8
8
|
create(context) {
|
|
9
9
|
const builder = new InitialSessionContextBuilder(agent.profileName)
|
|
@@ -12,7 +12,7 @@ export function createCustomAgentProfileDefinition(agent, options = {}) {
|
|
|
12
12
|
.withAutoContextFiles(agent.autoContextFiles)
|
|
13
13
|
.withMcpServers(agent.mcpServers)
|
|
14
14
|
.withPiPackages(agent.piPackages.map((id) => ({ id })))
|
|
15
|
-
.withToolPackages({ runControl: agent.runControl });
|
|
15
|
+
.withToolPackages({ runControl: agent.runControl, goalControl: agent.goalControl ?? true });
|
|
16
16
|
if (agent.mainModel)
|
|
17
17
|
builder.withMainModel(agent.mainModel);
|
|
18
18
|
if (agent.subagentModel)
|
|
@@ -42,6 +42,7 @@ export class CustomAgentStore {
|
|
|
42
42
|
builtin_tool_names_json TEXT NOT NULL DEFAULT '["read","bash","edit","write"]',
|
|
43
43
|
auto_context_files INTEGER NOT NULL DEFAULT 1,
|
|
44
44
|
run_control INTEGER NOT NULL,
|
|
45
|
+
goal_control INTEGER NOT NULL DEFAULT 1,
|
|
45
46
|
created_at TEXT NOT NULL,
|
|
46
47
|
updated_at TEXT NOT NULL,
|
|
47
48
|
archived_at TEXT
|
|
@@ -57,6 +58,7 @@ export class CustomAgentStore {
|
|
|
57
58
|
this.migrateThinkingLevelColumn();
|
|
58
59
|
this.migrateThinkingOptionColumns();
|
|
59
60
|
this.migrateBuiltinToolNamesColumn();
|
|
61
|
+
this.migrateGoalControlColumn();
|
|
60
62
|
this.migrateAgentHistory();
|
|
61
63
|
this.migrateLegacyProfileNames();
|
|
62
64
|
this.migrateDuplicateProfileNames();
|
|
@@ -104,6 +106,7 @@ export class CustomAgentStore {
|
|
|
104
106
|
builtinToolNames: sanitizeBuiltinToolNames(input.builtinToolNames),
|
|
105
107
|
autoContextFiles: input.autoContextFiles ?? true,
|
|
106
108
|
runControl: input.runControl ?? false,
|
|
109
|
+
goalControl: input.goalControl ?? true,
|
|
107
110
|
createdAt: now,
|
|
108
111
|
updatedAt: now,
|
|
109
112
|
};
|
|
@@ -143,6 +146,7 @@ export class CustomAgentStore {
|
|
|
143
146
|
builtinToolNames: input.builtinToolNames ? sanitizeBuiltinToolNames(input.builtinToolNames) : existing.builtinToolNames,
|
|
144
147
|
autoContextFiles: input.autoContextFiles ?? existing.autoContextFiles,
|
|
145
148
|
runControl: input.runControl ?? existing.runControl,
|
|
149
|
+
goalControl: input.goalControl ?? existing.goalControl,
|
|
146
150
|
updatedAt: new Date().toISOString(),
|
|
147
151
|
};
|
|
148
152
|
this.db
|
|
@@ -169,10 +173,11 @@ export class CustomAgentStore {
|
|
|
169
173
|
builtin_tool_names_json = ?,
|
|
170
174
|
auto_context_files = ?,
|
|
171
175
|
run_control = ?,
|
|
176
|
+
goal_control = ?,
|
|
172
177
|
updated_at = ?
|
|
173
178
|
WHERE id = ?
|
|
174
179
|
`)
|
|
175
|
-
.run(updated.profileName, updated.displayName, updated.description ?? null, JSON.stringify(updated.nativeTools), JSON.stringify(updated.skills), JSON.stringify(updated.contextFiles), JSON.stringify(sanitizeSubagents(updated.subagents)), JSON.stringify(updated.mcpServers), JSON.stringify(updated.piPackages), updated.mainModel ? JSON.stringify(updated.mainModel) : null, updated.subagentModel ? JSON.stringify(updated.subagentModel) : null, updated.thinkingLevel ?? null, updated.mainThinkingLevel ?? null, updated.subagentThinkingLevel ?? null, serializeBoolean(updated.fast), serializeBoolean(updated.mainFast), serializeBoolean(updated.subagentFast), updated.builtinTools, JSON.stringify(updated.builtinToolNames), updated.autoContextFiles ? 1 : 0, updated.runControl ? 1 : 0, updated.updatedAt, id);
|
|
180
|
+
.run(updated.profileName, updated.displayName, updated.description ?? null, JSON.stringify(updated.nativeTools), JSON.stringify(updated.skills), JSON.stringify(updated.contextFiles), JSON.stringify(sanitizeSubagents(updated.subagents)), JSON.stringify(updated.mcpServers), JSON.stringify(updated.piPackages), updated.mainModel ? JSON.stringify(updated.mainModel) : null, updated.subagentModel ? JSON.stringify(updated.subagentModel) : null, updated.thinkingLevel ?? null, updated.mainThinkingLevel ?? null, updated.subagentThinkingLevel ?? null, serializeBoolean(updated.fast), serializeBoolean(updated.mainFast), serializeBoolean(updated.subagentFast), updated.builtinTools, JSON.stringify(updated.builtinToolNames), updated.autoContextFiles ? 1 : 0, updated.runControl ? 1 : 0, updated.goalControl ? 1 : 0, updated.updatedAt, id);
|
|
176
181
|
return this.get(id);
|
|
177
182
|
}
|
|
178
183
|
setArchived(id, archived) {
|
|
@@ -220,12 +225,13 @@ export class CustomAgentStore {
|
|
|
220
225
|
builtin_tool_names_json,
|
|
221
226
|
auto_context_files,
|
|
222
227
|
run_control,
|
|
228
|
+
goal_control,
|
|
223
229
|
created_at,
|
|
224
230
|
updated_at,
|
|
225
231
|
archived_at
|
|
226
|
-
) VALUES (${Array.from({ length:
|
|
232
|
+
) VALUES (${Array.from({ length: 26 }, () => "?").join(", ")})
|
|
227
233
|
`)
|
|
228
|
-
.run(agent.id, agent.profileName, agent.displayName, agent.description ?? null, JSON.stringify(agent.nativeTools), JSON.stringify(agent.skills), JSON.stringify(agent.contextFiles), JSON.stringify(sanitizeSubagents(agent.subagents)), JSON.stringify(agent.mcpServers), JSON.stringify(agent.piPackages), agent.mainModel ? JSON.stringify(agent.mainModel) : null, agent.subagentModel ? JSON.stringify(agent.subagentModel) : null, agent.thinkingLevel ?? null, agent.mainThinkingLevel ?? null, agent.subagentThinkingLevel ?? null, serializeBoolean(agent.fast), serializeBoolean(agent.mainFast), serializeBoolean(agent.subagentFast), agent.builtinTools, JSON.stringify(agent.builtinToolNames), agent.autoContextFiles ? 1 : 0, agent.runControl ? 1 : 0, agent.createdAt, agent.updatedAt, agent.archivedAt ?? null);
|
|
234
|
+
.run(agent.id, agent.profileName, agent.displayName, agent.description ?? null, JSON.stringify(agent.nativeTools), JSON.stringify(agent.skills), JSON.stringify(agent.contextFiles), JSON.stringify(sanitizeSubagents(agent.subagents)), JSON.stringify(agent.mcpServers), JSON.stringify(agent.piPackages), agent.mainModel ? JSON.stringify(agent.mainModel) : null, agent.subagentModel ? JSON.stringify(agent.subagentModel) : null, agent.thinkingLevel ?? null, agent.mainThinkingLevel ?? null, agent.subagentThinkingLevel ?? null, serializeBoolean(agent.fast), serializeBoolean(agent.mainFast), serializeBoolean(agent.subagentFast), agent.builtinTools, JSON.stringify(agent.builtinToolNames), agent.autoContextFiles ? 1 : 0, agent.runControl ? 1 : 0, agent.goalControl ? 1 : 0, agent.createdAt, agent.updatedAt, agent.archivedAt ?? null);
|
|
229
235
|
}
|
|
230
236
|
requireProfileNameAvailable(profileName, currentId) {
|
|
231
237
|
const row = this.db.prepare("SELECT id FROM chat_agents WHERE profile_name = ?").get(profileName);
|
|
@@ -380,6 +386,12 @@ export class CustomAgentStore {
|
|
|
380
386
|
this.db.prepare("ALTER TABLE chat_agents ADD COLUMN builtin_tool_names_json TEXT NOT NULL DEFAULT '[\"read\",\"bash\",\"edit\",\"write\"]'").run();
|
|
381
387
|
}
|
|
382
388
|
}
|
|
389
|
+
migrateGoalControlColumn() {
|
|
390
|
+
const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
|
|
391
|
+
if (!columns.has("goal_control")) {
|
|
392
|
+
this.db.prepare("ALTER TABLE chat_agents ADD COLUMN goal_control INTEGER NOT NULL DEFAULT 1").run();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
383
395
|
migrateAgentHistory() {
|
|
384
396
|
this.db.exec(`
|
|
385
397
|
CREATE TABLE IF NOT EXISTS chat_agent_events (
|
|
@@ -542,6 +554,7 @@ function agentFromRow(row, profileAliases) {
|
|
|
542
554
|
builtinToolNames: sanitizeBuiltinToolNames(parseStringArray(row.builtin_tool_names_json)),
|
|
543
555
|
autoContextFiles: row.auto_context_files !== 0,
|
|
544
556
|
runControl: row.run_control === 1,
|
|
557
|
+
goalControl: row.goal_control !== 0,
|
|
545
558
|
createdAt: row.created_at,
|
|
546
559
|
updatedAt: row.updated_at,
|
|
547
560
|
archivedAt: row.archived_at ?? undefined,
|
|
@@ -193,6 +193,13 @@ export function normalizeRunControl(value) {
|
|
|
193
193
|
throw new PiboWebHttpError("runControl must be a boolean", 400);
|
|
194
194
|
return value;
|
|
195
195
|
}
|
|
196
|
+
export function normalizeGoalControl(value) {
|
|
197
|
+
if (value === undefined)
|
|
198
|
+
return true;
|
|
199
|
+
if (typeof value !== "boolean")
|
|
200
|
+
throw new PiboWebHttpError("goalControl must be a boolean", 400);
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
196
203
|
export function normalizeOptionalBoolean(value, fieldName) {
|
|
197
204
|
if (value === undefined || value === null)
|
|
198
205
|
return undefined;
|
|
@@ -611,6 +618,7 @@ export function createAgentInput(body) {
|
|
|
611
618
|
builtinToolNames: normalizeBuiltinToolNames(body.builtinToolNames),
|
|
612
619
|
autoContextFiles: normalizeAutoContextFiles(body.autoContextFiles),
|
|
613
620
|
runControl: normalizeRunControl(body.runControl),
|
|
621
|
+
goalControl: normalizeGoalControl(body.goalControl),
|
|
614
622
|
};
|
|
615
623
|
}
|
|
616
624
|
export function createAgentUpdate(body) {
|
|
@@ -655,6 +663,8 @@ export function createAgentUpdate(body) {
|
|
|
655
663
|
update.autoContextFiles = normalizeAutoContextFiles(body.autoContextFiles);
|
|
656
664
|
if (body.runControl !== undefined)
|
|
657
665
|
update.runControl = normalizeRunControl(body.runControl);
|
|
666
|
+
if (body.goalControl !== undefined)
|
|
667
|
+
update.goalControl = normalizeGoalControl(body.goalControl);
|
|
658
668
|
if (Object.keys(update).length === 0 && body.archived === undefined) {
|
|
659
669
|
throw new PiboWebHttpError("No agent update fields provided", 400);
|
|
660
670
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messageTurnTimingsFromEvents } from "../../../shared/trace-event-projection.js";
|
|
1
2
|
import { storedChatEventFromV2Row, storedPiboEventFromV2Row } from "./chat-data-mappers.js";
|
|
2
3
|
export class ChatTimelineQueryService {
|
|
3
4
|
store;
|
|
@@ -30,6 +31,16 @@ export class ChatTimelineQueryService {
|
|
|
30
31
|
listAllSessionEvents(piboSessionId) {
|
|
31
32
|
return this.listTraceEvents({ piboSessionId, limit: 10000, includeLive: true });
|
|
32
33
|
}
|
|
34
|
+
listMessageTurnTimings(piboSessionId) {
|
|
35
|
+
const rows = this.store.db.prepare(`
|
|
36
|
+
SELECT * FROM event_log
|
|
37
|
+
WHERE session_id = ?
|
|
38
|
+
AND type IN ('message_started', 'message_finished')
|
|
39
|
+
ORDER BY session_sequence ASC, stream_id ASC
|
|
40
|
+
`).all(piboSessionId);
|
|
41
|
+
const events = rows.map(storedPiboEventFromV2Row).filter((event) => event !== undefined);
|
|
42
|
+
return messageTurnTimingsFromEvents(events);
|
|
43
|
+
}
|
|
33
44
|
listTraceEvents(input) {
|
|
34
45
|
const piboSessionId = typeof input === "string" ? input : input.piboSessionId;
|
|
35
46
|
const limit = Math.max(1, Math.min((typeof input === "string" ? undefined : input.limit) ?? 2000, 10000));
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { PiboWebHttpError, readJsonBody, responseJson } from '../../web/http.js';
|
|
2
|
+
import { getPiboLoopService } from '../../loops/channel.js';
|
|
3
|
+
import { listLoopJobTemplates } from '../../loops/templates.js';
|
|
4
|
+
import { isPiboThinkingLevel } from '../../core/thinking.js';
|
|
5
|
+
import { normalizeLoopStopPolicy } from '../../loops/store.js';
|
|
6
|
+
import { isPiboRoomArchived } from './types/rooms.js';
|
|
7
|
+
const CHAT_WEB_API_PREFIX = '/api/chat';
|
|
8
|
+
function requireSameOriginJsonRequest(request) { const contentType = request.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase(); if (contentType !== 'application/json')
|
|
9
|
+
throw new PiboWebHttpError('Content-Type must be application/json', 415); const origin = request.headers.get('origin'); if (!origin)
|
|
10
|
+
throw new PiboWebHttpError('Origin header is required', 403); if (origin !== new URL(request.url).origin)
|
|
11
|
+
throw new PiboWebHttpError('Origin is not allowed', 403); }
|
|
12
|
+
function accessDenied(error) { throw new PiboWebHttpError(error instanceof Error ? error.message : 'Access denied', 403); }
|
|
13
|
+
function normalizeString(value, field, options = {}) { if (value === undefined || value === null) {
|
|
14
|
+
if (options.required)
|
|
15
|
+
throw new PiboWebHttpError(`${field} is required`, 400);
|
|
16
|
+
return undefined;
|
|
17
|
+
} if (typeof value !== 'string')
|
|
18
|
+
throw new PiboWebHttpError(`${field} must be a string`, 400); const normalized = value.trim(); if (!normalized && options.required)
|
|
19
|
+
throw new PiboWebHttpError(`${field} is required`, 400); if (options.max && normalized.length > options.max)
|
|
20
|
+
throw new PiboWebHttpError(`${field} is too long`, 400); return normalized || undefined; }
|
|
21
|
+
function normalizeEnabled(value) { if (value === undefined)
|
|
22
|
+
return undefined; if (typeof value !== 'boolean')
|
|
23
|
+
throw new PiboWebHttpError('enabled must be a boolean', 400); return value; }
|
|
24
|
+
function normalizeMode(value, fallback) { if (value === undefined || value === null || value === '')
|
|
25
|
+
return fallback; if (value !== 'goal' && value !== 'ralph')
|
|
26
|
+
throw new PiboWebHttpError('mode must be goal or ralph', 400); return value; }
|
|
27
|
+
function normalizeMaxIterations(value) { if (value === undefined || value === null || value === '')
|
|
28
|
+
return undefined; if (typeof value !== 'number' || !Number.isInteger(value) || value < 1)
|
|
29
|
+
throw new PiboWebHttpError('maxIterations must be a positive integer', 400); return value; }
|
|
30
|
+
function normalizeTokenBudget(value) { if (value === undefined || value === null || value === '')
|
|
31
|
+
return undefined; if (typeof value !== 'number' || !Number.isInteger(value) || value < 1)
|
|
32
|
+
throw new PiboWebHttpError('tokenBudget must be a positive integer', 400); return value; }
|
|
33
|
+
function normalizeStopPolicy(value) { if (value === undefined || value === null)
|
|
34
|
+
return undefined; try {
|
|
35
|
+
return normalizeLoopStopPolicy(value);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new PiboWebHttpError(error instanceof Error ? error.message : 'Invalid stopPolicy', 400);
|
|
39
|
+
} }
|
|
40
|
+
function normalizeModelOverride(value) { if (value === undefined || value === null)
|
|
41
|
+
return undefined; if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
42
|
+
throw new PiboWebHttpError('modelOverride must be an object', 400); const raw = value; if (typeof raw.provider !== 'string' || typeof raw.id !== 'string')
|
|
43
|
+
throw new PiboWebHttpError('modelOverride must include provider and id', 400); const provider = raw.provider.trim(); const id = raw.id.trim(); if (!provider || !id)
|
|
44
|
+
throw new PiboWebHttpError('modelOverride must include provider and id', 400); return { provider, id }; }
|
|
45
|
+
function normalizeThinkingLevel(value) { if (value === undefined || value === null || value === '')
|
|
46
|
+
return undefined; if (typeof value !== 'string' || !isPiboThinkingLevel(value))
|
|
47
|
+
throw new PiboWebHttpError('thinkingLevel must be one of off, minimal, low, medium, high, xhigh, max', 400); return value; }
|
|
48
|
+
function normalizeFastMode(value) { if (value === undefined || value === null)
|
|
49
|
+
return undefined; if (typeof value !== 'boolean')
|
|
50
|
+
throw new PiboWebHttpError('fastMode must be a boolean', 400); return value; }
|
|
51
|
+
function normalizeTarget(value, options) { if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
52
|
+
throw new PiboWebHttpError('target is required', 400); const raw = value; if (raw.kind === 'room') {
|
|
53
|
+
const roomId = normalizeString(raw.roomId, 'target.roomId', { required: true });
|
|
54
|
+
let room;
|
|
55
|
+
try {
|
|
56
|
+
room = options.roomService.requireRoom(roomId);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
accessDenied(error);
|
|
60
|
+
}
|
|
61
|
+
if (isPiboRoomArchived(room))
|
|
62
|
+
throw new PiboWebHttpError('Archived rooms are read-only', 403);
|
|
63
|
+
return { kind: 'room', roomId };
|
|
64
|
+
} if (raw.kind === 'default-chat') {
|
|
65
|
+
options.roomService.ensureDefaultRoom({ name: 'Shared Chat' });
|
|
66
|
+
return { kind: 'default-chat' };
|
|
67
|
+
} throw new PiboWebHttpError('target.kind must be room or default-chat', 400); }
|
|
68
|
+
function resolveProfile(context, fallback, value) { const requested = normalizeString(value, 'profile') ?? fallback; const profile = context.channelContext.getProfiles?.().find((item) => item.name === requested || item.aliases.includes(requested)); if (!profile)
|
|
69
|
+
throw new PiboWebHttpError(`Unknown profile: ${requested}`, 400); return profile.name; }
|
|
70
|
+
function jobResource(pathname) { const prefix = `${CHAT_WEB_API_PREFIX}/loops/jobs/`; if (!pathname.startsWith(prefix))
|
|
71
|
+
return undefined; const parts = pathname.slice(prefix.length).split('/').filter(Boolean).map((part) => decodeURIComponent(part)); if (!parts[0] || parts.length > 2)
|
|
72
|
+
return undefined; if (parts[1] && !['start', 'stop', 'cancel'].includes(parts[1]))
|
|
73
|
+
return undefined; return { id: parts[0], child: parts[1] }; }
|
|
74
|
+
function createPatch(body, options) { const patch = {}; if (body.mode !== undefined)
|
|
75
|
+
patch.mode = normalizeMode(body.mode, 'goal'); const name = normalizeString(body.name, 'name', { max: 120 }); if (body.name !== undefined && name !== undefined)
|
|
76
|
+
patch.name = name; if (body.description !== undefined)
|
|
77
|
+
patch.description = normalizeString(body.description, 'description', { max: 500 }); const enabled = normalizeEnabled(body.enabled); if (enabled !== undefined)
|
|
78
|
+
patch.enabled = enabled; if (body.target !== undefined)
|
|
79
|
+
patch.target = normalizeTarget(body.target, options); if (body.profile !== undefined)
|
|
80
|
+
patch.profile = resolveProfile(options.context, options.defaultProfile, body.profile); if (body.prompt !== undefined)
|
|
81
|
+
patch.prompt = normalizeString(body.prompt, 'prompt', { required: true, max: 20_000 }); if (body.maxIterations !== undefined)
|
|
82
|
+
patch.maxIterations = normalizeMaxIterations(body.maxIterations) ?? null; if (body.tokenBudget !== undefined)
|
|
83
|
+
patch.tokenBudget = normalizeTokenBudget(body.tokenBudget) ?? null; if (body.stopPolicy !== undefined)
|
|
84
|
+
patch.stopPolicy = normalizeStopPolicy(body.stopPolicy) ?? null; if (body.modelOverride !== undefined)
|
|
85
|
+
patch.modelOverride = normalizeModelOverride(body.modelOverride) ?? null; if (body.thinkingLevel !== undefined)
|
|
86
|
+
patch.thinkingLevel = normalizeThinkingLevel(body.thinkingLevel) ?? null; if (body.fastMode !== undefined)
|
|
87
|
+
patch.fastMode = normalizeFastMode(body.fastMode) ?? null; if (Object.keys(patch).length === 0)
|
|
88
|
+
throw new PiboWebHttpError('No Loop job update fields provided', 400); return patch; }
|
|
89
|
+
function serializeTarget(target) { return target.kind === 'room' ? target : { kind: 'default-chat' }; }
|
|
90
|
+
function serializeJob(job) { const { target, ...rest } = job; return { ...rest, target: serializeTarget(target) }; }
|
|
91
|
+
function serializeRun(run) { return run; }
|
|
92
|
+
export async function handleChatLoopApiRequest(options) {
|
|
93
|
+
const { request, loopStore } = options;
|
|
94
|
+
const url = new URL(request.url);
|
|
95
|
+
const legacyRalphRequest = url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/ralph`);
|
|
96
|
+
if (!legacyRalphRequest && !url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/loops`) && !url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/loop`))
|
|
97
|
+
return undefined;
|
|
98
|
+
const apiPath = url.pathname.replace(/^\/api\/chat\/(?:ralph|loop)(?=\/|$)/, `${CHAT_WEB_API_PREFIX}/loops`);
|
|
99
|
+
if (apiPath === `${CHAT_WEB_API_PREFIX}/loops/status` && request.method === 'GET')
|
|
100
|
+
return responseJson({ status: getPiboLoopService()?.status() ?? { enabled: false, ...loopStore.status() } });
|
|
101
|
+
if (apiPath === `${CHAT_WEB_API_PREFIX}/loops/conditions` && request.method === 'GET')
|
|
102
|
+
return responseJson({ conditions: options.context.channelContext.getLoopStopConditionInfos?.() ?? options.context.channelContext.getCapabilityCatalog?.().loopStopConditions ?? [] });
|
|
103
|
+
if (apiPath === `${CHAT_WEB_API_PREFIX}/loops/templates` && request.method === 'GET')
|
|
104
|
+
return responseJson({ templates: listLoopJobTemplates() });
|
|
105
|
+
if (apiPath === `${CHAT_WEB_API_PREFIX}/loops/jobs` && request.method === 'GET')
|
|
106
|
+
return responseJson({ jobs: loopStore.listJobs({ includeDisabled: url.searchParams.get('includeDisabled') === 'true' }).map(serializeJob) });
|
|
107
|
+
if (apiPath === `${CHAT_WEB_API_PREFIX}/loops/jobs` && request.method === 'POST') {
|
|
108
|
+
requireSameOriginJsonRequest(request);
|
|
109
|
+
const body = await readJsonBody(request);
|
|
110
|
+
const mode = normalizeMode(body.mode, legacyRalphRequest ? 'ralph' : 'goal');
|
|
111
|
+
const tokenBudget = normalizeTokenBudget(body.tokenBudget);
|
|
112
|
+
if (mode === 'ralph' && tokenBudget !== undefined)
|
|
113
|
+
throw new PiboWebHttpError('tokenBudget is only available for goal mode', 400);
|
|
114
|
+
const job = loopStore.createJob({ mode, name: normalizeString(body.name, 'name', { max: 120 }), description: normalizeString(body.description, 'description', { max: 500 }), enabled: normalizeEnabled(body.enabled), target: normalizeTarget(body.target, options), profile: resolveProfile(options.context, options.defaultProfile, body.profile), prompt: normalizeString(body.prompt, 'prompt', { required: true, max: 20_000 }), maxIterations: normalizeMaxIterations(body.maxIterations), tokenBudget, stopPolicy: normalizeStopPolicy(body.stopPolicy), modelOverride: normalizeModelOverride(body.modelOverride), thinkingLevel: normalizeThinkingLevel(body.thinkingLevel), fastMode: normalizeFastMode(body.fastMode) });
|
|
115
|
+
return responseJson({ job: serializeJob(job) }, { status: 201 });
|
|
116
|
+
}
|
|
117
|
+
if (apiPath === `${CHAT_WEB_API_PREFIX}/loops/runs` && request.method === 'GET') {
|
|
118
|
+
const jobId = url.searchParams.get('jobId') || undefined;
|
|
119
|
+
const limit = Number(url.searchParams.get('limit') ?? '100');
|
|
120
|
+
if (jobId && !loopStore.getJob(jobId))
|
|
121
|
+
throw new PiboWebHttpError('Loop job not found', 404);
|
|
122
|
+
return responseJson({ runs: loopStore.listRuns({ jobId, limit: Number.isFinite(limit) ? limit : 100 }).map(serializeRun) });
|
|
123
|
+
}
|
|
124
|
+
const resource = jobResource(apiPath);
|
|
125
|
+
if (!resource)
|
|
126
|
+
return undefined;
|
|
127
|
+
if (resource.child && request.method === 'POST') {
|
|
128
|
+
requireSameOriginJsonRequest(request);
|
|
129
|
+
const service = getPiboLoopService();
|
|
130
|
+
if (!service)
|
|
131
|
+
throw new PiboWebHttpError('Loop service is not running', 503);
|
|
132
|
+
if (resource.child === 'start') {
|
|
133
|
+
const run = await service.startJob(resource.id);
|
|
134
|
+
if (!run)
|
|
135
|
+
throw new PiboWebHttpError('Loop job not found, already running, or stopped by a before-run condition', 404);
|
|
136
|
+
return responseJson({ run: serializeRun(run) }, { status: 202 });
|
|
137
|
+
}
|
|
138
|
+
if (resource.child === 'stop') {
|
|
139
|
+
const job = service.stopJob(resource.id);
|
|
140
|
+
if (!job)
|
|
141
|
+
throw new PiboWebHttpError('Loop job not found', 404);
|
|
142
|
+
return responseJson({ job: serializeJob(job) });
|
|
143
|
+
}
|
|
144
|
+
const job = await service.cancelJob(resource.id);
|
|
145
|
+
if (!job)
|
|
146
|
+
throw new PiboWebHttpError('Loop job not found', 404);
|
|
147
|
+
return responseJson({ job: serializeJob(job) });
|
|
148
|
+
}
|
|
149
|
+
if (resource.child)
|
|
150
|
+
return undefined;
|
|
151
|
+
if (request.method === 'GET') {
|
|
152
|
+
const job = loopStore.getJob(resource.id);
|
|
153
|
+
if (!job)
|
|
154
|
+
throw new PiboWebHttpError('Loop job not found', 404);
|
|
155
|
+
return responseJson({ job: serializeJob(job) });
|
|
156
|
+
}
|
|
157
|
+
if (request.method === 'PATCH') {
|
|
158
|
+
requireSameOriginJsonRequest(request);
|
|
159
|
+
const body = await readJsonBody(request);
|
|
160
|
+
const patch = createPatch(body, options);
|
|
161
|
+
const existing = loopStore.getJob(resource.id);
|
|
162
|
+
if (!existing)
|
|
163
|
+
throw new PiboWebHttpError('Loop job not found', 404);
|
|
164
|
+
if ((patch.mode ?? existing.mode) === 'ralph' && patch.tokenBudget !== undefined && patch.tokenBudget !== null)
|
|
165
|
+
throw new PiboWebHttpError('tokenBudget is only available for goal mode', 400);
|
|
166
|
+
const job = loopStore.updateJob(resource.id, patch);
|
|
167
|
+
if (!job)
|
|
168
|
+
throw new PiboWebHttpError('Loop job not found', 404);
|
|
169
|
+
return responseJson({ job: serializeJob(job) });
|
|
170
|
+
}
|
|
171
|
+
if (request.method === 'DELETE') {
|
|
172
|
+
requireSameOriginJsonRequest(request);
|
|
173
|
+
return responseJson({ removed: loopStore.removeJob(resource.id) });
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
package/dist/apps/chat/trace.js
CHANGED
|
@@ -126,6 +126,7 @@ export async function buildTraceView(input) {
|
|
|
126
126
|
title: createSessionTitle(input.session, metadata),
|
|
127
127
|
},
|
|
128
128
|
events: input.events,
|
|
129
|
+
turnTimings: input.turnTimings,
|
|
129
130
|
transcriptEntries: allEntries,
|
|
130
131
|
sessions: input.sessions.map((s) => ({
|
|
131
132
|
id: s.id,
|
|
@@ -175,6 +176,7 @@ export function createTraceViewVersion(input) {
|
|
|
175
176
|
const eventTail = input.events.at(-1);
|
|
176
177
|
return createHash("sha1")
|
|
177
178
|
.update(JSON.stringify({
|
|
179
|
+
traceProjection: "turn-timing-v2",
|
|
178
180
|
session: {
|
|
179
181
|
id: input.session.id,
|
|
180
182
|
piSessionId: input.session.piSessionId,
|
|
@@ -33,9 +33,9 @@ import { ChatTimelineQueryService } from "./data/timeline-query-service.js";
|
|
|
33
33
|
import { ChatProjectService } from "./data/project-service.js";
|
|
34
34
|
import { PiboDataStore } from "../../data/pibo-store.js";
|
|
35
35
|
import { createDefaultPiboCronStore } from "../../cron/store.js";
|
|
36
|
-
import {
|
|
36
|
+
import { createDefaultPiboLoopStore } from "../../loops/store.js";
|
|
37
37
|
import { handleChatCronApiRequest } from "./cron-api.js";
|
|
38
|
-
import {
|
|
38
|
+
import { handleChatLoopApiRequest } from "./loop-api.js";
|
|
39
39
|
import { prepareWebAnnotationMessageAttachments } from "../../web-annotations/attachments.js";
|
|
40
40
|
import { createDefaultWebAnnotationStore } from "../../web-annotations/store.js";
|
|
41
41
|
import { CHAT_WEB_MOUNT_PATH, isChatAppPath, responseBuiltChatAsset, responseBuiltChatPublicFile, responseChatAppShell, isVscodeAppPath, responseBuiltVscodeAsset, responseVscodeAppShell } from "./static-assets.js";
|
|
@@ -323,6 +323,7 @@ function createFastTraceV2Version(input) {
|
|
|
323
323
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
324
324
|
return createHash("sha1")
|
|
325
325
|
.update(JSON.stringify({
|
|
326
|
+
traceProjection: "turn-timing-v2",
|
|
326
327
|
session: {
|
|
327
328
|
id: input.session.id,
|
|
328
329
|
piSessionId: input.session.piSessionId,
|
|
@@ -3033,7 +3034,7 @@ export function createChatWebApp(options = {}) {
|
|
|
3033
3034
|
agentStore: createAgentStore(options.agentStorePath),
|
|
3034
3035
|
reliabilityStore: createReliabilityStore(options.reliabilityStorePath),
|
|
3035
3036
|
cronStore: createDefaultPiboCronStore({ path: options.cronStorePath }),
|
|
3036
|
-
|
|
3037
|
+
loopStore: createDefaultPiboLoopStore({ path: options.ralphStorePath }),
|
|
3037
3038
|
dataStore,
|
|
3038
3039
|
ingestService: new ChatDataIngestService(dataStore),
|
|
3039
3040
|
traceCache: new Map(),
|
|
@@ -3221,14 +3222,14 @@ export function createChatWebApp(options = {}) {
|
|
|
3221
3222
|
if (response)
|
|
3222
3223
|
return response;
|
|
3223
3224
|
}
|
|
3224
|
-
if (url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/ralph`)) {
|
|
3225
|
+
if (url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/loops`) || url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/loop`) || url.pathname.startsWith(`${CHAT_WEB_API_PREFIX}/ralph`)) {
|
|
3225
3226
|
const webSession = await requireSession(request, context);
|
|
3226
|
-
const response = await
|
|
3227
|
+
const response = await handleChatLoopApiRequest({
|
|
3227
3228
|
request,
|
|
3228
3229
|
context,
|
|
3229
3230
|
webSession,
|
|
3230
3231
|
roomService: state.roomService,
|
|
3231
|
-
|
|
3232
|
+
loopStore: state.loopStore,
|
|
3232
3233
|
defaultProfile,
|
|
3233
3234
|
});
|
|
3234
3235
|
if (response)
|
|
@@ -4266,6 +4267,7 @@ export function createChatWebApp(options = {}) {
|
|
|
4266
4267
|
let metadataMs = 0;
|
|
4267
4268
|
const lastEventSequence = state.timelineQuery.getLatestEventSequence(selectedSession.id);
|
|
4268
4269
|
const latestStreamId = state.timelineQuery.getLatestStreamId({ piboSessionId: selectedSession.id });
|
|
4270
|
+
const turnTimings = state.timelineQuery.listMessageTurnTimings(selectedSession.id);
|
|
4269
4271
|
const liveSnapshots = timelineCursor.kind === "tail" ? state.outputCompactor.snapshotsForSession(selectedSession.id) : [];
|
|
4270
4272
|
const metadataStartedAt = performance.now();
|
|
4271
4273
|
const transcriptMetadata = timelineCursor.kind === "tail" || timelineCursor.kind === "transcript"
|
|
@@ -4328,6 +4330,7 @@ export function createChatWebApp(options = {}) {
|
|
|
4328
4330
|
metadata: transcriptMetadata ?? {},
|
|
4329
4331
|
transcriptEntries: history.entries,
|
|
4330
4332
|
transcriptOrderOffset: history.startByte,
|
|
4333
|
+
turnTimings,
|
|
4331
4334
|
includeRawEvents: false,
|
|
4332
4335
|
latestStreamId,
|
|
4333
4336
|
});
|
|
@@ -4360,6 +4363,7 @@ export function createChatWebApp(options = {}) {
|
|
|
4360
4363
|
status: indexedSession?.status,
|
|
4361
4364
|
metadata: transcriptMetadata ?? {},
|
|
4362
4365
|
transcriptEntries,
|
|
4366
|
+
turnTimings,
|
|
4363
4367
|
includeRawEvents: false,
|
|
4364
4368
|
latestStreamId,
|
|
4365
4369
|
});
|
|
@@ -4474,6 +4478,7 @@ export function createChatWebApp(options = {}) {
|
|
|
4474
4478
|
const metadataMs = performance.now() - metadataStartedAt;
|
|
4475
4479
|
const lastEventSequence = state.timelineQuery.getLatestEventSequence(selectedSession.id);
|
|
4476
4480
|
const latestStreamId = state.timelineQuery.getLatestStreamId({ piboSessionId: selectedSession.id });
|
|
4481
|
+
const turnTimings = state.timelineQuery.listMessageTurnTimings(selectedSession.id);
|
|
4477
4482
|
const liveSnapshots = beforeSequence === undefined ? state.outputCompactor.snapshotsForSession(selectedSession.id) : [];
|
|
4478
4483
|
const baseVersion = createTraceViewVersion({
|
|
4479
4484
|
session: selectedSession,
|
|
@@ -4517,6 +4522,7 @@ export function createChatWebApp(options = {}) {
|
|
|
4517
4522
|
events,
|
|
4518
4523
|
status: indexedSession?.status,
|
|
4519
4524
|
metadata,
|
|
4525
|
+
turnTimings,
|
|
4520
4526
|
includeRawEvents: false,
|
|
4521
4527
|
latestStreamId,
|
|
4522
4528
|
});
|
|
@@ -4578,6 +4584,7 @@ export function createChatWebApp(options = {}) {
|
|
|
4578
4584
|
sessions: ownedSessions,
|
|
4579
4585
|
events: state.timelineQuery.listTraceEvents({ piboSessionId, beforeOrAtSequence: eventSequence, limit: DEFAULT_TRACE_EVENTS_PAGE_SIZE }),
|
|
4580
4586
|
status: indexedSession?.status,
|
|
4587
|
+
turnTimings: state.timelineQuery.listMessageTurnTimings(piboSessionId),
|
|
4581
4588
|
});
|
|
4582
4589
|
return responseJson(trace);
|
|
4583
4590
|
}
|