@junctionpanel/server 0.1.53 → 0.1.55

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 (71) hide show
  1. package/dist/server/client/daemon-client.d.ts +30 -1
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +236 -24
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  6. package/dist/server/server/agent/activity-curator.js +12 -0
  7. package/dist/server/server/agent/activity-curator.js.map +1 -1
  8. package/dist/server/server/agent/agent-manager.d.ts +8 -0
  9. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-manager.js +175 -29
  11. package/dist/server/server/agent/agent-manager.js.map +1 -1
  12. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-metadata-generator.js +11 -2
  14. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  15. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-projections.js +0 -1
  17. package/dist/server/server/agent/agent-projections.js.map +1 -1
  18. package/dist/server/server/agent/agent-response-loop.d.ts +27 -0
  19. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  20. package/dist/server/server/agent/agent-response-loop.js +78 -4
  21. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  22. package/dist/server/server/agent/agent-sdk-types.d.ts +17 -1
  23. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  25. package/dist/server/server/agent/agent-storage.d.ts +0 -3
  26. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  27. package/dist/server/server/agent/agent-storage.js +0 -1
  28. package/dist/server/server/agent/agent-storage.js.map +1 -1
  29. package/dist/server/server/agent/provider-model-cache.d.ts +14 -0
  30. package/dist/server/server/agent/provider-model-cache.d.ts.map +1 -0
  31. package/dist/server/server/agent/provider-model-cache.js +71 -0
  32. package/dist/server/server/agent/provider-model-cache.js.map +1 -0
  33. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -1
  34. package/dist/server/server/agent/providers/claude/model-catalog.js +15 -1
  35. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -1
  36. package/dist/server/server/agent/providers/claude-agent.d.ts +1 -0
  37. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  38. package/dist/server/server/agent/providers/claude-agent.js +43 -12
  39. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  40. package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
  41. package/dist/server/server/agent/providers/gemini-agent.js +36 -1
  42. package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
  43. package/dist/server/server/agent/providers/opencode-agent.js +8 -2
  44. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  45. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  46. package/dist/server/server/file-explorer/service.d.ts +6 -1
  47. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  48. package/dist/server/server/file-explorer/service.js +72 -23
  49. package/dist/server/server/file-explorer/service.js.map +1 -1
  50. package/dist/server/server/persistence-hooks.js +11 -1
  51. package/dist/server/server/persistence-hooks.js.map +1 -1
  52. package/dist/server/server/session.d.ts +38 -0
  53. package/dist/server/server/session.d.ts.map +1 -1
  54. package/dist/server/server/session.js +956 -207
  55. package/dist/server/server/session.js.map +1 -1
  56. package/dist/server/server/tool-call-preview.d.ts.map +1 -1
  57. package/dist/server/server/tool-call-preview.js +5 -3
  58. package/dist/server/server/tool-call-preview.js.map +1 -1
  59. package/dist/server/shared/messages.d.ts +7630 -2138
  60. package/dist/server/shared/messages.d.ts.map +1 -1
  61. package/dist/server/shared/messages.js +64 -0
  62. package/dist/server/shared/messages.js.map +1 -1
  63. package/dist/server/shared/permission-questions.d.ts +43 -0
  64. package/dist/server/shared/permission-questions.d.ts.map +1 -0
  65. package/dist/server/shared/permission-questions.js +234 -0
  66. package/dist/server/shared/permission-questions.js.map +1 -0
  67. package/dist/server/utils/checkout-git.d.ts +1 -0
  68. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  69. package/dist/server/utils/checkout-git.js +19 -6
  70. package/dist/server/utils/checkout-git.js.map +1 -1
  71. package/package.json +2 -2
@@ -8,7 +8,16 @@ import { AGENT_PROVIDER_IDS } from "./provider-manifest.js";
8
8
  import { buildPermissionRecoveryFingerprint } from "./agent-permission-fingerprint.js";
9
9
  import { isCodexPlanModeEnabled, normalizeCodexModeId, setCodexPlanModeEnabled, } from "./codex-config.js";
10
10
  import { getPendingPlanReviewFingerprint, hasPendingPlanReview, } from "./pending-plan-review.js";
11
+ import { extractPermissionQuestionAnswersFromResponse, formatPermissionQuestionAnswers, isInteractiveQuestionPermission, } from "../../shared/permission-questions.js";
11
12
  export { AGENT_LIFECYCLE_STATUSES };
13
+ function toPersistedPermissionResolution(resolution) {
14
+ return resolution.behavior === "deny"
15
+ ? {
16
+ behavior: "deny",
17
+ message: resolution.message,
18
+ }
19
+ : { behavior: "allow" };
20
+ }
12
21
  function attachPersistenceCwd(handle, cwd) {
13
22
  if (!handle) {
14
23
  return null;
@@ -157,6 +166,8 @@ export class AgentManager {
157
166
  this.pendingUserInterruptAgents = new Set();
158
167
  this.userInterruptedAgents = new Set();
159
168
  this.backgroundTasks = new Set();
169
+ this.backgroundPersistQueuedAgents = new Set();
170
+ this.backgroundPersistDirtyAgents = new Set();
160
171
  this.liveEventPumps = new Map();
161
172
  this.liveEventBacklog = new Map();
162
173
  this.liveEventBacklogFlushTimers = new Map();
@@ -511,6 +522,9 @@ export class AgentManager {
511
522
  const session = handle
512
523
  ? await client.resumeSession(handle, normalizedConfig)
513
524
  : await client.createSession(normalizedConfig);
525
+ if (this.backgroundPersistQueuedAgents.has(agentId)) {
526
+ this.backgroundPersistDirtyAgents.add(agentId);
527
+ }
514
528
  // Remove the existing agent entry before swapping sessions
515
529
  this.agents.delete(agentId);
516
530
  this.liveEventPumps.delete(agentId);
@@ -552,6 +566,8 @@ export class AgentManager {
552
566
  this.suppressedPermissionAttentionKeys.delete(agentId);
553
567
  this.pendingUserInterruptAgents.delete(agentId);
554
568
  this.userInterruptedAgents.delete(agentId);
569
+ this.backgroundPersistQueuedAgents.delete(agentId);
570
+ this.backgroundPersistDirtyAgents.delete(agentId);
555
571
  const session = agent.session;
556
572
  const closedAgent = {
557
573
  ...agent,
@@ -927,9 +943,18 @@ export class AgentManager {
927
943
  async respondToPermission(agentId, requestId, response) {
928
944
  const agent = this.requireAgent(agentId);
929
945
  const pendingRequest = agent.pendingPermissions.get(requestId) ?? null;
930
- await agent.session.respondToPermission(requestId, response);
931
- agent.pendingPermissions.delete(requestId);
932
- this.clearAttentionForUserEngagement(agent);
946
+ if (pendingRequest &&
947
+ response.behavior === "allow" &&
948
+ isInteractiveQuestionPermission(pendingRequest)) {
949
+ const answers = extractPermissionQuestionAnswersFromResponse(pendingRequest, response);
950
+ const summary = formatPermissionQuestionAnswers(pendingRequest, answers).trim();
951
+ if (summary.length > 0) {
952
+ this.recordUserMessage(agentId, summary, {
953
+ emitState: false,
954
+ messageId: `permission-answer:${requestId}`,
955
+ });
956
+ }
957
+ }
933
958
  if (pendingRequest?.kind === "plan" &&
934
959
  response.behavior === "deny" &&
935
960
  typeof response.message === "string") {
@@ -941,6 +966,14 @@ export class AgentManager {
941
966
  });
942
967
  }
943
968
  }
969
+ await agent.session.respondToPermission(requestId, response);
970
+ agent.pendingPermissions.delete(requestId);
971
+ this.clearAttentionForUserEngagement(agent);
972
+ this.recordPermissionResolutionTimelineItem(agent, requestId, response, {
973
+ provider: agent.provider,
974
+ pendingRequest,
975
+ dispatchTimeline: true,
976
+ });
944
977
  // Update currentModeId - the session may have changed mode internally
945
978
  // (e.g., plan approval changes mode from "plan" to "acceptEdits")
946
979
  try {
@@ -1008,15 +1041,14 @@ export class AgentManager {
1008
1041
  // block before it consumes the turn_canceled event, skipping our cleanup code.
1009
1042
  if (agent.pendingPermissions.size > 0) {
1010
1043
  for (const [requestId] of agent.pendingPermissions) {
1011
- this.dispatchStream(agent.id, {
1012
- type: "permission_resolved",
1044
+ this.resolvePendingPermission(agent, requestId, { behavior: "deny", message: "Interrupted" }, {
1013
1045
  provider: agent.provider,
1014
- requestId,
1015
- resolution: { behavior: "deny", message: "Interrupted" },
1046
+ dispatchEvent: true,
1047
+ dispatchTimeline: true,
1016
1048
  });
1017
1049
  }
1018
- agent.pendingPermissions.clear();
1019
1050
  this.emitState(agent);
1051
+ await this.persistSnapshot(agent);
1020
1052
  }
1021
1053
  return true;
1022
1054
  }
@@ -1249,7 +1281,9 @@ export class AgentManager {
1249
1281
  this.userInterruptedAgents.delete(resolvedAgentId);
1250
1282
  const initialPersistedTitle = await this.resolveInitialPersistedTitle(resolvedAgentId, config);
1251
1283
  const now = new Date();
1252
- const initialTimeline = options?.timeline ? [...options.timeline] : [];
1284
+ const initialTimeline = options?.timeline
1285
+ ? [...options.timeline]
1286
+ : options?.timelineRows?.map((row) => row.item) ?? [];
1253
1287
  const initialTimelineRows = options?.timelineRows?.length
1254
1288
  ? options.timelineRows.map((row) => ({ ...row }))
1255
1289
  : this.buildTimelineRowsFromItems(initialTimeline, options?.timelineNextSeq ?? 1, (options?.updatedAt ?? options?.createdAt ?? now).toISOString());
@@ -1416,6 +1450,7 @@ export class AgentManager {
1416
1450
  }
1417
1451
  return [[messageId, row.item.text]];
1418
1452
  }));
1453
+ const initialTimelineRowCount = agent.timelineRows.length;
1419
1454
  try {
1420
1455
  for await (const event of agent.session.streamHistory()) {
1421
1456
  if (event.type === "timeline") {
@@ -1438,6 +1473,9 @@ export class AgentManager {
1438
1473
  catch {
1439
1474
  // ignore history failures
1440
1475
  }
1476
+ if (agent.timelineRows.length > initialTimelineRowCount) {
1477
+ await this.persistSnapshot(agent);
1478
+ }
1441
1479
  }
1442
1480
  handleStreamEvent(agent, event, options) {
1443
1481
  // Only update timestamp for live events, not history replay
@@ -1445,6 +1483,7 @@ export class AgentManager {
1445
1483
  this.touchUpdatedAt(agent);
1446
1484
  }
1447
1485
  let timelineRow = null;
1486
+ let shouldPersistSnapshot = false;
1448
1487
  switch (event.type) {
1449
1488
  case "thread_started":
1450
1489
  // Update persistence with the new session ID from the provider.
@@ -1458,6 +1497,7 @@ export class AgentManager {
1458
1497
  this.emitState(agent);
1459
1498
  }
1460
1499
  }
1500
+ shouldPersistSnapshot = true;
1461
1501
  }
1462
1502
  break;
1463
1503
  case "timeline":
@@ -1479,6 +1519,7 @@ export class AgentManager {
1479
1519
  break;
1480
1520
  }
1481
1521
  timelineRow = this.recordTimeline(agent, event.item);
1522
+ shouldPersistSnapshot = true;
1482
1523
  if (!options?.fromHistory &&
1483
1524
  event.item.type === "user_message") {
1484
1525
  agent.lastUserMessageAt = new Date();
@@ -1501,6 +1542,7 @@ export class AgentManager {
1501
1542
  seq: timelineRow.seq,
1502
1543
  epoch: this.ensureTimelineState(agent).epoch,
1503
1544
  });
1545
+ shouldPersistSnapshot = true;
1504
1546
  }
1505
1547
  }
1506
1548
  agent.activeTurnStartedAt = null;
@@ -1512,6 +1554,7 @@ export class AgentManager {
1512
1554
  this.emitState(agent);
1513
1555
  }
1514
1556
  void this.refreshRuntimeInfo(agent);
1557
+ shouldPersistSnapshot = true;
1515
1558
  }
1516
1559
  break;
1517
1560
  case "turn_failed":
@@ -1523,17 +1566,14 @@ export class AgentManager {
1523
1566
  agent.activeTurnStartedAt = null;
1524
1567
  agent.lastError = event.error;
1525
1568
  for (const [requestId] of agent.pendingPermissions) {
1526
- agent.pendingPermissions.delete(requestId);
1527
- if (!options?.fromHistory) {
1528
- this.dispatchStream(agent.id, {
1529
- type: "permission_resolved",
1530
- provider: event.provider,
1531
- requestId,
1532
- resolution: { behavior: "deny", message: "Turn failed" },
1533
- });
1534
- }
1569
+ this.resolvePendingPermission(agent, requestId, { behavior: "deny", message: "Turn failed" }, {
1570
+ provider: event.provider,
1571
+ dispatchEvent: !options?.fromHistory,
1572
+ dispatchTimeline: !options?.fromHistory,
1573
+ });
1535
1574
  }
1536
1575
  this.emitState(agent);
1576
+ shouldPersistSnapshot = true;
1537
1577
  break;
1538
1578
  case "turn_canceled":
1539
1579
  {
@@ -1549,17 +1589,15 @@ export class AgentManager {
1549
1589
  agent.lastError = undefined;
1550
1590
  if (!pendingRefresh.hasPendingPermissions) {
1551
1591
  for (const requestId of pendingRequestIds) {
1552
- if (!options?.fromHistory) {
1553
- this.dispatchStream(agent.id, {
1554
- type: "permission_resolved",
1555
- provider: event.provider,
1556
- requestId,
1557
- resolution: { behavior: "deny", message: "Interrupted" },
1558
- });
1559
- }
1592
+ this.resolvePendingPermission(agent, requestId, { behavior: "deny", message: "Interrupted" }, {
1593
+ provider: event.provider,
1594
+ dispatchEvent: !options?.fromHistory,
1595
+ dispatchTimeline: !options?.fromHistory,
1596
+ });
1560
1597
  }
1561
1598
  }
1562
1599
  this.emitState(agent);
1600
+ shouldPersistSnapshot = true;
1563
1601
  }
1564
1602
  break;
1565
1603
  case "turn_started":
@@ -1579,10 +1617,15 @@ export class AgentManager {
1579
1617
  agent.lifecycle = "running";
1580
1618
  }
1581
1619
  this.emitState(agent);
1620
+ shouldPersistSnapshot = true;
1582
1621
  break;
1583
1622
  case "permission_requested":
1584
1623
  {
1585
1624
  agent.pendingPermissions.set(event.request.id, event.request);
1625
+ this.recordPermissionRequestTimelineItem(agent, event.request, {
1626
+ provider: event.provider,
1627
+ dispatchTimeline: !options?.fromHistory,
1628
+ });
1586
1629
  const planText = extractPlanTextFromPermissionRequest(event.request);
1587
1630
  if (planText && !options?.fromHistory) {
1588
1631
  const planMessage = formatProposedPlanTimelineMessage(planText);
@@ -1603,10 +1646,16 @@ export class AgentManager {
1603
1646
  }
1604
1647
  }
1605
1648
  this.emitState(agent);
1649
+ shouldPersistSnapshot = true;
1606
1650
  break;
1607
1651
  case "permission_resolved":
1608
- agent.pendingPermissions.delete(event.requestId);
1652
+ this.resolvePendingPermission(agent, event.requestId, event.resolution, {
1653
+ provider: event.provider,
1654
+ dispatchEvent: false,
1655
+ dispatchTimeline: !options?.fromHistory,
1656
+ });
1609
1657
  this.emitState(agent);
1658
+ shouldPersistSnapshot = true;
1610
1659
  break;
1611
1660
  default:
1612
1661
  break;
@@ -1620,6 +1669,9 @@ export class AgentManager {
1620
1669
  }
1621
1670
  : undefined);
1622
1671
  }
1672
+ if (!options?.fromHistory && shouldPersistSnapshot) {
1673
+ this.enqueueBackgroundPersist(agent);
1674
+ }
1623
1675
  }
1624
1676
  shouldSuppressLiveUserMessageEcho(agent, event, options) {
1625
1677
  if (options?.fromHistory || event.type !== "timeline") {
@@ -1661,6 +1713,81 @@ export class AgentManager {
1661
1713
  ...(durationMs != null ? { durationMs } : {}),
1662
1714
  };
1663
1715
  }
1716
+ findRecordedPermissionRequest(agent, requestId) {
1717
+ for (let index = agent.timelineRows.length - 1; index >= 0; index -= 1) {
1718
+ const row = agent.timelineRows[index];
1719
+ if (row?.item.type === "permission_request" &&
1720
+ row.item.request.id === requestId) {
1721
+ return structuredClone(row.item.request);
1722
+ }
1723
+ }
1724
+ return null;
1725
+ }
1726
+ hasPermissionResolutionTimelineItem(agent, requestId) {
1727
+ return agent.timelineRows.some((row) => row.item.type === "permission_resolution" &&
1728
+ row.item.requestId === requestId);
1729
+ }
1730
+ recordPermissionRequestTimelineItem(agent, request, options) {
1731
+ if (request.kind === "plan") {
1732
+ return null;
1733
+ }
1734
+ if (this.findRecordedPermissionRequest(agent, request.id)) {
1735
+ return null;
1736
+ }
1737
+ const item = {
1738
+ type: "permission_request",
1739
+ request: structuredClone(request),
1740
+ };
1741
+ const row = this.recordTimeline(agent, item);
1742
+ if (options.dispatchTimeline) {
1743
+ this.dispatchStream(agent.id, { type: "timeline", provider: options.provider, item }, {
1744
+ seq: row.seq,
1745
+ epoch: this.ensureTimelineState(agent).epoch,
1746
+ });
1747
+ }
1748
+ return row;
1749
+ }
1750
+ recordPermissionResolutionTimelineItem(agent, requestId, resolution, options) {
1751
+ const request = options.pendingRequest ??
1752
+ agent.pendingPermissions.get(requestId) ??
1753
+ this.findRecordedPermissionRequest(agent, requestId);
1754
+ if (!request || request.kind === "plan") {
1755
+ return null;
1756
+ }
1757
+ if (this.hasPermissionResolutionTimelineItem(agent, requestId)) {
1758
+ return null;
1759
+ }
1760
+ const item = {
1761
+ type: "permission_resolution",
1762
+ requestId,
1763
+ resolution: toPersistedPermissionResolution(resolution),
1764
+ };
1765
+ const row = this.recordTimeline(agent, item);
1766
+ if (options.dispatchTimeline) {
1767
+ this.dispatchStream(agent.id, { type: "timeline", provider: options.provider, item }, {
1768
+ seq: row.seq,
1769
+ epoch: this.ensureTimelineState(agent).epoch,
1770
+ });
1771
+ }
1772
+ return row;
1773
+ }
1774
+ resolvePendingPermission(agent, requestId, resolution, options) {
1775
+ const pendingRequest = agent.pendingPermissions.get(requestId) ?? null;
1776
+ agent.pendingPermissions.delete(requestId);
1777
+ this.recordPermissionResolutionTimelineItem(agent, requestId, resolution, {
1778
+ provider: options.provider,
1779
+ pendingRequest,
1780
+ dispatchTimeline: options.dispatchTimeline,
1781
+ });
1782
+ if (options.dispatchEvent) {
1783
+ this.dispatchStream(agent.id, {
1784
+ type: "permission_resolved",
1785
+ provider: options.provider,
1786
+ requestId,
1787
+ resolution,
1788
+ });
1789
+ }
1790
+ }
1664
1791
  recordTimeline(agent, item) {
1665
1792
  const timelineState = this.ensureTimelineState(agent);
1666
1793
  const row = {
@@ -1805,8 +1932,27 @@ export class AgentManager {
1805
1932
  this.suppressedPermissionAttentionKeys.delete(agent.id);
1806
1933
  }
1807
1934
  enqueueBackgroundPersist(agent) {
1808
- const task = this.persistSnapshot(agent).catch((err) => {
1809
- this.logger.error({ err, agentId: agent.id }, "Failed to persist agent snapshot");
1935
+ this.enqueueBackgroundPersistById(agent.id);
1936
+ }
1937
+ enqueueBackgroundPersistById(agentId) {
1938
+ if (this.backgroundPersistQueuedAgents.has(agentId)) {
1939
+ this.backgroundPersistDirtyAgents.add(agentId);
1940
+ return;
1941
+ }
1942
+ const currentAgent = this.agents.get(agentId);
1943
+ if (!currentAgent) {
1944
+ return;
1945
+ }
1946
+ this.backgroundPersistQueuedAgents.add(agentId);
1947
+ const task = this.persistSnapshot(currentAgent)
1948
+ .catch((err) => {
1949
+ this.logger.error({ err, agentId }, "Failed to persist agent snapshot");
1950
+ })
1951
+ .finally(() => {
1952
+ this.backgroundPersistQueuedAgents.delete(agentId);
1953
+ if (this.backgroundPersistDirtyAgents.delete(agentId)) {
1954
+ this.enqueueBackgroundPersistById(agentId);
1955
+ }
1810
1956
  });
1811
1957
  this.trackBackgroundTask(task);
1812
1958
  }