@pasko70/pibo 3.2.6 → 3.3.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.
Files changed (37) hide show
  1. package/dist/agent-runtime/history.js +5 -16
  2. package/dist/agent-runtimes/codex-native/adapter.js +44 -14
  3. package/dist/agent-runtimes/codex-native/process.js +1 -1
  4. package/dist/agent-runtimes/codex-native/protocol-version.js +6 -6
  5. package/dist/agent-runtimes/omp/process.js +23 -3
  6. package/dist/agent-runtimes/omp/protocol-version.js +2 -0
  7. package/dist/agent-runtimes/pi/adapter.js +1 -1
  8. package/dist/agent-runtimes/pi/model-catalog.js +2 -2
  9. package/dist/agent-runtimes/pi/runtime.js +6 -3
  10. package/dist/apps/chat/chat-api-routes.js +1 -1
  11. package/dist/apps/chat/chat-request-normalizers.js +7 -2
  12. package/dist/apps/chat/data/read-state-service.js +13 -11
  13. package/dist/apps/chat/session-metadata.js +30 -0
  14. package/dist/apps/chat/trace.js +15 -2
  15. package/dist/apps/chat/web-app.js +125 -17
  16. package/dist/apps/chat-ui/assets/{dist-CLxh9uSM.js → dist-BbCjOLiw.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-B9gnHZqL.js → dist-Bd9gWCbk.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-CR75V9VE.js → dist-C9a0_446.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-BUupvbV9.js → dist-CcAkJ8iv.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-DXdSiTyu.js → dist-Kc3zbvQi.js} +1 -1
  21. package/dist/apps/chat-ui/assets/index-DWJnUye8.css +1 -0
  22. package/dist/apps/chat-ui/assets/index-exohEOUE.js +228 -0
  23. package/dist/apps/chat-ui/index.html +2 -2
  24. package/dist/core/session-router.js +12 -6
  25. package/dist/debug/agents.js +9 -3
  26. package/dist/plugins/builtin.js +2 -0
  27. package/dist/plugins/registry.js +1 -0
  28. package/dist/providers/openai-gpt56.js +41 -3
  29. package/dist/subagents/context.js +2 -2
  30. package/dist/subagents/observation-query.js +49 -6
  31. package/dist/subagents/observation-text-regex.js +124 -0
  32. package/dist/subagents/tool.js +3 -2
  33. package/dist/tools/hashline.js +64 -0
  34. package/npm-shrinkwrap.json +781 -280
  35. package/package.json +6 -5
  36. package/dist/apps/chat-ui/assets/index-BMkUGVRS.js +0 -228
  37. package/dist/apps/chat-ui/assets/index-BwT5grLv.css +0 -1
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  /**
2
3
  * Build a structural complete-history claim for compatibility callers.
3
4
  *
@@ -17,24 +18,12 @@ export function createCompleteHistoryReconciliationProof(entries, scopeId) {
17
18
  };
18
19
  }
19
20
  export function historyReconciliationDigest(entries) {
20
- const mask = 0xffffffffffffffffn;
21
- const prime = 0x100000001b3n;
22
- const hashes = [
23
- 0xcbf29ce484222325n,
24
- 0x84222325cbf29ce4n,
25
- 0x9e3779b97f4a7c15n,
26
- 0x517cc1b727220a95n,
27
- ];
21
+ const hash = createHash("sha256");
28
22
  for (const entry of entries) {
29
- const signature = `${historyReconciliationEntrySignature(entry)}\n`;
30
- for (let index = 0; index < signature.length; index += 1) {
31
- const codeUnit = BigInt(signature.charCodeAt(index));
32
- for (let hashIndex = 0; hashIndex < hashes.length; hashIndex += 1) {
33
- hashes[hashIndex] = ((hashes[hashIndex] ^ codeUnit) * prime) & mask;
34
- }
35
- }
23
+ hash.update(historyReconciliationEntrySignature(entry));
24
+ hash.update("\n");
36
25
  }
37
- return hashes.map((hash) => hash.toString(16).padStart(16, "0")).join("");
26
+ return hash.digest("hex");
38
27
  }
39
28
  export function historyReconciliationEntrySignature(entry) {
40
29
  const { sequence: _pageLocalSequence, ...proofContent } = entry;
@@ -112,7 +112,7 @@ function codexNativeCapabilities(structuredUserInput) {
112
112
  nativeToolInspection: {
113
113
  support: "degraded",
114
114
  mode: "observed-runtime-items",
115
- reason: "Stable Codex App Server 0.147.0 does not expose a complete pre-turn native-tool inventory; Pibo reports selected MCP tools immediately and harness-native tools after stable item notifications prove they are active.",
115
+ reason: "Stable Codex App Server 0.153.2 does not expose a complete pre-turn native-tool inventory; Pibo reports selected MCP tools immediately and harness-native tools after stable item notifications prove they are active.",
116
116
  },
117
117
  nativeToolYielding: unsupportedAgentRuntimeCapability("Codex native tools remain harness-owned and are not wrapped as Pibo yielded tools."),
118
118
  intentTracing: {
@@ -776,6 +776,7 @@ export class CodexNativeThreadSession {
776
776
  }
777
777
  const codexNativeCompleteHistoryProofs = new WeakMap();
778
778
  const codexNativeHistoryReaders = new WeakMap();
779
+ const CODEX_NATIVE_HISTORY_INSPECTION_CACHE_TTL_MS = 5_000;
779
780
  const startCodexNativeHistoryProcess = startCodexNativeAppServer;
780
781
  const readCodexNativeHistoryThread = CodexNativeThreadController.read;
781
782
  function bindCodexNativeCompleteHistoryProof(page) {
@@ -807,6 +808,7 @@ class CodexNativeAgentRuntimeAdapter {
807
808
  displayName;
808
809
  enabled;
809
810
  modelCatalogCache;
811
+ historyInspectionCache = new Map();
810
812
  authController;
811
813
  constructor(instanceId, config, displayName, enabled) {
812
814
  this.instanceId = instanceId;
@@ -871,6 +873,29 @@ class CodexNativeAgentRuntimeAdapter {
871
873
  async disposeAuth() {
872
874
  await this.authController.dispose();
873
875
  }
876
+ historyInspectionCacheKey(input, threadId) {
877
+ return `${input.binding.piboSessionId}\0${input.workspace}\0${threadId}`;
878
+ }
879
+ cacheHistoryInspection(cacheKey, inspection) {
880
+ this.historyInspectionCache.set(cacheKey, {
881
+ expiresAt: Date.now() + CODEX_NATIVE_HISTORY_INSPECTION_CACHE_TTL_MS,
882
+ value: Promise.resolve(inspection),
883
+ });
884
+ }
885
+ async readHistoryInspection(input, threadId) {
886
+ try {
887
+ const readHistory = codexNativeHistoryReaders.get(this);
888
+ if (!readHistory)
889
+ throw new Error("Codex built-in history reader is unavailable.");
890
+ const thread = await readHistory(input.binding.piboSessionId, input.workspace, threadId, false);
891
+ return inspectCodexThreadHistory(this.instanceId, input.binding, thread);
892
+ }
893
+ catch (error) {
894
+ return unavailableCodexThreadHistoryInspection(this.instanceId, input.binding, error instanceof CodexNativeThreadMissingError ? "codex_native_history_not_found" : "codex_native_history_unavailable", error instanceof CodexNativeThreadMissingError
895
+ ? "The bound Codex thread is unavailable for native history inspection."
896
+ : "Codex native history inspection failed safely.");
897
+ }
898
+ }
874
899
  validateProfile(input) {
875
900
  const diagnostics = [];
876
901
  if (input.profile.runtimeInstanceId !== this.instanceId) {
@@ -1123,18 +1148,18 @@ class CodexNativeAgentRuntimeAdapter {
1123
1148
  if (!threadId) {
1124
1149
  return unavailableCodexThreadHistoryInspection(this.instanceId, input.binding, "codex_native_history_thread_id_missing", "The Codex runtime binding has no native thread id for history lookup.");
1125
1150
  }
1126
- try {
1127
- const readHistory = codexNativeHistoryReaders.get(this);
1128
- if (!readHistory)
1129
- throw new Error("Codex built-in history reader is unavailable.");
1130
- const thread = await readHistory(input.binding.piboSessionId, input.workspace, threadId, false);
1131
- return inspectCodexThreadHistory(this.instanceId, input.binding, thread);
1132
- }
1133
- catch (error) {
1134
- return unavailableCodexThreadHistoryInspection(this.instanceId, input.binding, error instanceof CodexNativeThreadMissingError ? "codex_native_history_not_found" : "codex_native_history_unavailable", error instanceof CodexNativeThreadMissingError
1135
- ? "The bound Codex thread is unavailable for native history inspection."
1136
- : "Codex native history inspection failed safely.");
1151
+ const cacheKey = this.historyInspectionCacheKey(input, threadId);
1152
+ const cached = this.historyInspectionCache.get(cacheKey);
1153
+ if (cached && cached.expiresAt > Date.now())
1154
+ return await cached.value;
1155
+ const value = this.readHistoryInspection(input, threadId);
1156
+ const entry = { expiresAt: Date.now() + CODEX_NATIVE_HISTORY_INSPECTION_CACHE_TTL_MS, value };
1157
+ this.historyInspectionCache.set(cacheKey, entry);
1158
+ const inspection = await value;
1159
+ if (!inspection.available && this.historyInspectionCache.get(cacheKey) === entry) {
1160
+ this.historyInspectionCache.delete(cacheKey);
1137
1161
  }
1162
+ return inspection;
1138
1163
  }
1139
1164
  async readHistory(input) {
1140
1165
  const threadId = input.binding.nativeSessionId;
@@ -1149,21 +1174,26 @@ class CodexNativeAgentRuntimeAdapter {
1149
1174
  inspection,
1150
1175
  };
1151
1176
  }
1177
+ const cacheKey = this.historyInspectionCacheKey(input, threadId);
1152
1178
  try {
1153
1179
  const readHistory = codexNativeHistoryReaders.get(this);
1154
1180
  if (!readHistory)
1155
1181
  throw new Error("Codex built-in history reader is unavailable.");
1156
1182
  const thread = await readHistory(input.binding.piboSessionId, input.workspace, threadId, true);
1157
- return bindCodexNativeCompleteHistoryProof(pageCodexThreadHistory({
1183
+ const page = pageCodexThreadHistory({
1158
1184
  runtimeInstanceId: this.instanceId,
1159
1185
  binding: input.binding,
1160
1186
  thread,
1161
1187
  cursor: input.cursor,
1162
1188
  beforeTimestamp: input.beforeTimestamp,
1163
1189
  limit: input.limit,
1164
- }));
1190
+ });
1191
+ if (page.inspection)
1192
+ this.cacheHistoryInspection(cacheKey, page.inspection);
1193
+ return bindCodexNativeCompleteHistoryProof(page);
1165
1194
  }
1166
1195
  catch (error) {
1196
+ this.historyInspectionCache.delete(cacheKey);
1167
1197
  if (error instanceof CodexNativeThreadMissingError) {
1168
1198
  const inspection = unavailableCodexThreadHistoryInspection(this.instanceId, input.binding, "codex_native_history_not_found", "The bound Codex thread is unavailable for native history reads.");
1169
1199
  return {
@@ -277,7 +277,7 @@ function parseCodexVersion(output) {
277
277
  };
278
278
  }
279
279
  function versionSupported(version) {
280
- return !version.prerelease && version.major === 0 && version.minor === 147 && version.patch >= 0;
280
+ return !version.prerelease && version.major === 0 && version.minor === 153 && version.patch >= 2;
281
281
  }
282
282
  export async function diagnoseCodexNativeRuntime(config, runtimeInstanceId, options = {}) {
283
283
  const diagnostics = [];
@@ -1,7 +1,7 @@
1
- export const CODEX_APP_SERVER_VERSION = "0.147.0";
2
- export const CODEX_APP_SERVER_SUPPORTED_RANGE = ">=0.147.0 <0.148.0";
1
+ export const CODEX_APP_SERVER_VERSION = "0.153.2";
2
+ export const CODEX_APP_SERVER_SUPPORTED_RANGE = ">=0.153.2 <0.154.0";
3
3
  export const CODEX_APP_SERVER_PROTOCOL_NAME = "codex-app-server-v2";
4
- export const CODEX_APP_SERVER_SCHEMA_SHA256 = "f72b2caa3cbfa4298de9e85c62dda6dfbaf2266ffeb916fed30615ca69ff8c74";
5
- export const CODEX_APP_SERVER_V2_SCHEMA_SHA256 = "f3dec1e031d99a420b137b903f02196d4325eece57620c925bb7130b25f168d2";
6
- export const CODEX_APP_SERVER_GENERATED_TYPES_INDEX_SHA256 = "1cca50b4003a6661cd211dff7c655ecf03ecfe4c7bba8e72a5bbc9af33ee5086";
7
- export const CODEX_APP_SERVER_GENERATED_BUNDLE_SHA256 = "ab890a36f19a6b48284c34dd46bb5154ccc34185c4c4d0f05c16e0bf113956a1";
4
+ export const CODEX_APP_SERVER_SCHEMA_SHA256 = "e8284c5cb8157554a3dd1e035aadbd4325aea501af56887e9c2e12eb1b9b9448";
5
+ export const CODEX_APP_SERVER_V2_SCHEMA_SHA256 = "d3eace08be5dca386bfd1f1e8df650058b4113f1e10870a284d775d75517576a";
6
+ export const CODEX_APP_SERVER_GENERATED_TYPES_INDEX_SHA256 = "cad2c5b82900b6bc58cdc36fdb756e2ab8a49274147af96a7ca7ac2aecd0db08";
7
+ export const CODEX_APP_SERVER_GENERATED_BUNDLE_SHA256 = "7dc2b38ce79ce8e08014ae129363a4ae9626d1f0d8af47b960da11221eab5676";
@@ -4,6 +4,7 @@ import { mkdir, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { protectPrivateFileSync, protectPrivatePathsSync } from "../../core/private-path.js";
6
6
  import { OmpRpcClient } from "./client.js";
7
+ import { OMP_CLI_SUPPORTED_RANGE, OMP_CLI_VERSION } from "./protocol-version.js";
7
8
  const MAX_VERSION_OUTPUT_BYTES = 256 * 1024;
8
9
  const PRIVATE_DIRECTORY_MODE = 0o700;
9
10
  const PRIVATE_FILE_MODE = 0o600;
@@ -258,12 +259,31 @@ export async function diagnoseOmpRuntime(config, runtimeInstanceId, options = {}
258
259
  });
259
260
  return diagnostics;
260
261
  }
261
- const version = probe.output.trim().split("\n").slice(-1)[0] ?? "";
262
+ const versionOutput = probe.output.trim().split("\n").slice(-1)[0] ?? "";
263
+ const versionMatch = versionOutput.match(/\bomp(?:\/|\s+)v?(\d+\.\d+\.\d+)\b/i);
264
+ const version = versionMatch?.[1];
265
+ if (!version) {
266
+ diagnostics.push({
267
+ severity: "error",
268
+ code: "omp_version_unreadable",
269
+ message: `OMP CLI returned an unrecognized version for runtime instance "${runtimeInstanceId}".`,
270
+ });
271
+ return diagnostics;
272
+ }
273
+ if (version !== OMP_CLI_VERSION) {
274
+ diagnostics.push({
275
+ severity: "error",
276
+ code: "omp_version_unsupported",
277
+ message: `OMP CLI ${version} is outside the supported range for runtime instance "${runtimeInstanceId}".`,
278
+ details: { version, supportedRange: OMP_CLI_SUPPORTED_RANGE },
279
+ });
280
+ return diagnostics;
281
+ }
262
282
  diagnostics.push({
263
283
  severity: "info",
264
284
  code: "omp_version_ok",
265
- message: `OMP CLI is available for runtime instance "${runtimeInstanceId}".`,
266
- details: { version: version || "unknown", private: true },
285
+ message: `OMP CLI ${version} is available for runtime instance "${runtimeInstanceId}".`,
286
+ details: { version, supportedRange: OMP_CLI_SUPPORTED_RANGE, private: true },
267
287
  });
268
288
  return diagnostics;
269
289
  }
@@ -0,0 +1,2 @@
1
+ export const OMP_CLI_VERSION = "18.1.10";
2
+ export const OMP_CLI_SUPPORTED_RANGE = "=18.1.10";
@@ -14,7 +14,7 @@ import { importPortableHistoryIntoPi } from "./portable-history.js";
14
14
  import { piIntentTracingEnabled } from "./intent-tracing.js";
15
15
  import { isWebSearchProviderTool } from "../../tools/web-search.js";
16
16
  const PI_ADAPTER_ID = "pi";
17
- export const PI_PROTOCOL_VERSION = "0.84.2";
17
+ export const PI_PROTOCOL_VERSION = "0.85.0";
18
18
  const piCompleteHistoryProofs = new WeakMap();
19
19
  function bindPiCompleteHistoryProof(page) {
20
20
  const claim = page.reconciliationProof;
@@ -2,7 +2,7 @@ import { ModelRegistry, createAgentSessionServices, } from "@earendil-works/pi-c
2
2
  import { registerMiniMaxProvider } from "../../providers/minimax.js";
3
3
  import { registerGlmProvider } from "../../providers/glm.js";
4
4
  import { registerQwenTokenPlanProvider } from "../../providers/qwen-token-plan.js";
5
- import { registerOpenAiGpt56Models } from "../../providers/openai-gpt56.js";
5
+ import { registerOpenAiSupplementalModels } from "../../providers/openai-gpt56.js";
6
6
  import { piAuthMethodsForProvider } from "./auth.js";
7
7
  export function buildModelCatalogFromRegistry(registry) {
8
8
  const providers = new Map();
@@ -52,7 +52,7 @@ export async function loadModelCatalogWithServices(createServices, cwd = process
52
52
  }
53
53
  export async function loadModelCatalog(cwd = process.cwd()) {
54
54
  return loadModelCatalogWithServices(createAgentSessionServices, cwd, (registry) => {
55
- registerOpenAiGpt56Models(registry);
55
+ registerOpenAiSupplementalModels(registry);
56
56
  registerMiniMaxProvider(registry);
57
57
  registerGlmProvider(registry);
58
58
  registerQwenTokenPlanProvider(registry);
@@ -21,7 +21,7 @@ import { DEFAULT_USER_TIMEZONE } from "../../core/user-settings.js";
21
21
  import { registerMiniMaxProvider } from "../../providers/minimax.js";
22
22
  import { registerGlmProvider } from "../../providers/glm.js";
23
23
  import { registerQwenTokenPlanProvider } from "../../providers/qwen-token-plan.js";
24
- import { registerOpenAiGpt56Models } from "../../providers/openai-gpt56.js";
24
+ import { registerOpenAiSupplementalModels } from "../../providers/openai-gpt56.js";
25
25
  import { PIBO_APP_CONTEXT } from "../../app-context.js";
26
26
  import { RuntimeSessionRegistry } from "../../tools/runtime/registry.js";
27
27
  import { CodexBrowserSessionController } from "../../tools/codex-browser.js";
@@ -123,7 +123,10 @@ function getBuiltinToolAllowlist(profile, customTools) {
123
123
  if (profile.builtinTools === "disabled")
124
124
  return undefined;
125
125
  const defaultBuiltinTools = new Set(DEFAULT_BUILTIN_TOOL_NAMES);
126
- const selectedBuiltinTools = profile.builtinToolNames.filter((name) => defaultBuiltinTools.has(name));
126
+ const replacedBuiltinTools = new Set(profile.tools
127
+ .filter((tool) => tool.enabled !== false)
128
+ .flatMap((tool) => tool.replacesBuiltinTools ?? []));
129
+ const selectedBuiltinTools = profile.builtinToolNames.filter((name) => defaultBuiltinTools.has(name) && !replacedBuiltinTools.has(name));
127
130
  if (selectedBuiltinTools.length === DEFAULT_BUILTIN_TOOL_NAMES.length)
128
131
  return undefined;
129
132
  return [...selectedBuiltinTools, ...customTools.map((tool) => tool.name)];
@@ -253,7 +256,7 @@ export async function createPiboRuntime(options = {}) {
253
256
  runtimeSettingsManager = services.settingsManager;
254
257
  applyPiboRuntimeRetryDefaults(services.settingsManager, options.retryDefaults);
255
258
  const modelRegistry = new ModelRegistry(services.modelRuntime);
256
- registerOpenAiGpt56Models(modelRegistry);
259
+ registerOpenAiSupplementalModels(modelRegistry);
257
260
  registerMiniMaxProvider(modelRegistry);
258
261
  registerGlmProvider(modelRegistry);
259
262
  registerQwenTokenPlanProvider(modelRegistry);
@@ -314,7 +314,7 @@ export function sessionActionResource(pathname) {
314
314
  if (parts.length !== 2 || !parts[0])
315
315
  return undefined;
316
316
  const action = parts[1];
317
- if (action !== "read" && action !== "kill" && action !== "kill-all" && action !== "runtime-binding" && action !== "fork-candidates")
317
+ if (action !== "read" && action !== "kill" && action !== "kill-all" && action !== "runtime-binding" && action !== "fork-candidates" && action !== "order")
318
318
  return undefined;
319
319
  try {
320
320
  return { piboSessionId: decodeURIComponent(parts[0]), action };
@@ -5,7 +5,7 @@ import { DEFAULT_AGENT_RUNTIME_INSTANCE_ID } from "../../core/profiles.js";
5
5
  import { isPiboThinkingLevel } from "../../core/thinking.js";
6
6
  import { PiboWebHttpError } from "../../web/http.js";
7
7
  import { listPiPackages } from "../../pi-packages/store.js";
8
- import { withChatWebArchived } from "./session-metadata.js";
8
+ import { withChatWebArchived, withChatWebSessionPinned } from "./session-metadata.js";
9
9
  import { isDefaultPiboRoom, withPiboRoomArchived, withPiboRoomWorkspace } from "./types/rooms.js";
10
10
  import { isValidCustomAgentName } from "./agent-store.js";
11
11
  export function normalizeRoomName(value, fallback = "New Chat") {
@@ -670,7 +670,12 @@ export function createSessionUpdate(context, session, body) {
670
670
  const title = normalizeSessionTitle(body.title);
671
671
  if (title !== undefined)
672
672
  update.title = title;
673
- const metadata = metadataWithArchiveState(session, body.archived);
673
+ let metadata = metadataWithArchiveState(session, body.archived);
674
+ if (body.pinned !== undefined) {
675
+ if (typeof body.pinned !== "boolean")
676
+ throw new PiboWebHttpError("Session pinned flag must be boolean", 400);
677
+ metadata = withChatWebSessionPinned(metadata ?? session.metadata, body.pinned);
678
+ }
674
679
  if (metadata)
675
680
  update.metadata = metadata;
676
681
  if (body.profile !== undefined) {
@@ -16,21 +16,23 @@ export class ChatReadStateService {
16
16
  }
17
17
  countUnreadMessagesBySession(input) {
18
18
  const counts = new Map();
19
- if (!input.piboSessionIds.length)
19
+ const uniqueIds = [...new Set(input.piboSessionIds)];
20
+ if (!uniqueIds.length)
20
21
  return counts;
21
- for (let offset = 0; offset < input.piboSessionIds.length; offset += 400) {
22
- const ids = [...new Set(input.piboSessionIds)].slice(offset, offset + 400);
23
- const placeholders = ids.map(() => "?").join(", ");
22
+ for (let offset = 0; offset < uniqueIds.length; offset += 400) {
23
+ const ids = uniqueIds.slice(offset, offset + 400);
24
+ const requested = ids.map(() => "(?)").join(", ");
24
25
  const rows = this.store.db.prepare(`
26
+ WITH requested(session_id) AS (VALUES ${requested})
25
27
  SELECT e.session_id, COUNT(*) AS count
26
- FROM event_log e
27
- LEFT JOIN app_session_read_state reads ON reads.session_id = e.session_id
28
- WHERE e.session_id IN (${placeholders})
28
+ FROM requested r
29
+ LEFT JOIN app_session_read_state reads ON reads.session_id = r.session_id
30
+ JOIN event_log e
31
+ ON e.session_id = r.session_id
29
32
  AND e.stream_id > COALESCE(reads.last_read_stream_id, 0)
30
- AND (
31
- (e.retention_class = 'chat_message' AND e.type IN ('user.message.accepted', 'assistant_message'))
32
- OR e.type = 'session_error'
33
- )
33
+ WHERE
34
+ (e.retention_class = 'chat_message' AND e.type IN ('user.message.accepted', 'assistant_message'))
35
+ OR e.type = 'session_error'
34
36
  GROUP BY e.session_id
35
37
  `).all(...ids);
36
38
  for (const row of rows)
@@ -1,7 +1,26 @@
1
1
  const CHAT_WEB_ARCHIVED_AT_KEY = "chatWebArchivedAt";
2
+ const CHAT_WEB_PINNED_AT_KEY = "chatWebPinnedAt";
3
+ const CHAT_WEB_SIDEBAR_ORDER_KEY = "chatWebSidebarOrder";
2
4
  export function isChatWebSessionArchived(session) {
3
5
  return typeof session.metadata?.[CHAT_WEB_ARCHIVED_AT_KEY] === "string";
4
6
  }
7
+ export function isChatWebSessionPinned(session) {
8
+ return typeof session.metadata?.[CHAT_WEB_PINNED_AT_KEY] === "string";
9
+ }
10
+ export function chatWebSessionSidebarOrder(session) {
11
+ const value = session.metadata?.[CHAT_WEB_SIDEBAR_ORDER_KEY];
12
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
13
+ }
14
+ export function compareChatWebSessionsBySidebarOrder(left, right) {
15
+ const pinnedDifference = Number(isChatWebSessionPinned(right)) - Number(isChatWebSessionPinned(left));
16
+ if (pinnedDifference !== 0)
17
+ return pinnedDifference;
18
+ const leftOrder = chatWebSessionSidebarOrder(left) ?? (Date.parse(left.createdAt) || 0);
19
+ const rightOrder = chatWebSessionSidebarOrder(right) ?? (Date.parse(right.createdAt) || 0);
20
+ if (leftOrder !== rightOrder)
21
+ return rightOrder - leftOrder;
22
+ return right.id.localeCompare(left.id);
23
+ }
5
24
  export function withChatWebArchived(metadata, archived) {
6
25
  const next = { ...(metadata ?? {}) };
7
26
  if (archived) {
@@ -12,3 +31,14 @@ export function withChatWebArchived(metadata, archived) {
12
31
  }
13
32
  return next;
14
33
  }
34
+ export function withChatWebSessionPinned(metadata, pinned, order = Date.now()) {
35
+ const next = { ...(metadata ?? {}), [CHAT_WEB_SIDEBAR_ORDER_KEY]: order };
36
+ if (pinned)
37
+ next[CHAT_WEB_PINNED_AT_KEY] = new Date().toISOString();
38
+ else
39
+ delete next[CHAT_WEB_PINNED_AT_KEY];
40
+ return next;
41
+ }
42
+ export function withChatWebSessionSidebarOrder(metadata, order) {
43
+ return { ...(metadata ?? {}), [CHAT_WEB_SIDEBAR_ORDER_KEY]: order };
44
+ }
@@ -3,7 +3,7 @@ import { PI_HISTORY_PAGE_MAX_BYTES, PI_HISTORY_SCAN_MAX_BYTES, PI_HISTORY_TAIL_M
3
3
  import { isPiboThinkingLevel } from "../../core/thinking.js";
4
4
  import { isBuiltInHistoryReconciliationProof } from "../../agent-runtimes/history-proof.js";
5
5
  import { buildTraceViewFromEvents, traceNodesFromHistoryEntries } from "../../shared/trace-engine.js";
6
- import { isChatWebSessionArchived } from "./session-metadata.js";
6
+ import { chatWebSessionSidebarOrder, isChatWebSessionArchived, isChatWebSessionPinned } from "./session-metadata.js";
7
7
  import { workflowSessionKindFromMetadata } from "../../sessions/workflow-session-kind.js";
8
8
  export const TRACE_PROJECTION_VERSION = "runtime-history-v2-incomplete-status";
9
9
  export { compareTraceNodes, sortTraceNodes, nestTraceNodes, flattenTraceNodes, mapTraceNodesById, buildTraceViewFromEvents, traceNodesFromHistoryEntries, } from "../../shared/trace-engine.js";
@@ -34,7 +34,10 @@ export async function buildSessionNodes(sessions, indexItems, _cwd = process.cwd
34
34
  workflowSessionKind: workflowSessionKindFromMetadata(session.metadata),
35
35
  title: createSessionTitle(session, historyMetadataFromInspection(inspection)),
36
36
  subtitle: session.id,
37
+ createdAt: session.createdAt,
37
38
  archived: isChatWebSessionArchived(session),
39
+ pinned: isChatWebSessionPinned(session),
40
+ sidebarOrder: chatWebSessionSidebarOrder(session),
38
41
  status: sessionNodeStatus(indexed?.status),
39
42
  lastActivityAt: indexed?.lastActivityAt ?? indexed?.createdAt ?? session.createdAt,
40
43
  unreadCount: unreadCounts.get(session.id) || undefined,
@@ -70,7 +73,7 @@ export async function buildSessionNodes(sessions, indexItems, _cwd = process.cwd
70
73
  });
71
74
  }
72
75
  const sortNodes = (items) => {
73
- items.sort((left, right) => (right.lastActivityAt ?? "").localeCompare(left.lastActivityAt ?? ""));
76
+ items.sort(compareSessionNodesBySidebarOrder);
74
77
  for (const item of items) {
75
78
  item.derivedSessions.sort((left, right) => (right.lastActivityAt ?? "").localeCompare(left.lastActivityAt ?? ""));
76
79
  sortNodes(item.children);
@@ -79,6 +82,16 @@ export async function buildSessionNodes(sessions, indexItems, _cwd = process.cwd
79
82
  sortNodes(roots);
80
83
  return roots;
81
84
  }
85
+ function compareSessionNodesBySidebarOrder(left, right) {
86
+ const pinnedDifference = Number(Boolean(right.pinned)) - Number(Boolean(left.pinned));
87
+ if (pinnedDifference !== 0)
88
+ return pinnedDifference;
89
+ const leftOrder = left.sidebarOrder ?? (Date.parse(left.createdAt ?? "") || 0);
90
+ const rightOrder = right.sidebarOrder ?? (Date.parse(right.createdAt ?? "") || 0);
91
+ if (leftOrder !== rightOrder)
92
+ return rightOrder - leftOrder;
93
+ return right.piboSessionId.localeCompare(left.piboSessionId);
94
+ }
82
95
  function sessionNodeStatus(indexedStatus) {
83
96
  return indexedStatus ?? "idle";
84
97
  }