@junctionpanel/server 0.1.42 → 0.1.44

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 (34) hide show
  1. package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -1
  2. package/dist/server/client/daemon-client-websocket-transport.js +20 -1
  3. package/dist/server/client/daemon-client-websocket-transport.js.map +1 -1
  4. package/dist/server/client/daemon-client.d.ts +1 -0
  5. package/dist/server/client/daemon-client.d.ts.map +1 -1
  6. package/dist/server/client/daemon-client.js +1 -0
  7. package/dist/server/client/daemon-client.js.map +1 -1
  8. package/dist/server/server/agent/agent-manager.d.ts +1 -0
  9. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-manager.js +73 -31
  11. package/dist/server/server/agent/agent-manager.js.map +1 -1
  12. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  13. package/dist/server/server/agent/providers/codex-app-server-agent.js +125 -0
  14. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  15. package/dist/server/server/agent-attention-policy.d.ts +0 -5
  16. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  17. package/dist/server/server/agent-attention-policy.js +0 -9
  18. package/dist/server/server/agent-attention-policy.js.map +1 -1
  19. package/dist/server/server/notifications/relay-client.d.ts +3 -1
  20. package/dist/server/server/notifications/relay-client.d.ts.map +1 -1
  21. package/dist/server/server/notifications/relay-client.js +2 -1
  22. package/dist/server/server/notifications/relay-client.js.map +1 -1
  23. package/dist/server/server/session.d.ts +1 -0
  24. package/dist/server/server/session.d.ts.map +1 -1
  25. package/dist/server/server/session.js +1 -0
  26. package/dist/server/server/session.js.map +1 -1
  27. package/dist/server/server/websocket-server.d.ts.map +1 -1
  28. package/dist/server/server/websocket-server.js +26 -17
  29. package/dist/server/server/websocket-server.js.map +1 -1
  30. package/dist/server/shared/messages.d.ts +16 -0
  31. package/dist/server/shared/messages.d.ts.map +1 -1
  32. package/dist/server/shared/messages.js +1 -0
  33. package/dist/server/shared/messages.js.map +1 -1
  34. package/package.json +2 -2
@@ -11,6 +11,7 @@ import { buildCodexRuntimeExtra, DEFAULT_CODEX_MODE_ID, isCodexPlanModeEnabled,
11
11
  import { writeImageAttachment } from "./image-attachments.js";
12
12
  const DEFAULT_TIMEOUT_MS = 14 * 24 * 60 * 60 * 1000;
13
13
  const TURN_START_TIMEOUT_MS = 90 * 1000;
14
+ const TURN_COMPLETION_SETTLE_DELAY_MS = 75;
14
15
  const CODEX_PROVIDER = "codex";
15
16
  const CODEX_APP_SERVER_CAPABILITIES = {
16
17
  supportsStreaming: true,
@@ -1593,6 +1594,7 @@ class CodexAppServerAgentSession {
1593
1594
  this.pendingPlanTexts = new Map();
1594
1595
  this.cachedSkills = [];
1595
1596
  this.turnCompletionInFlight = false;
1597
+ this.deferredTurnCompletion = null;
1596
1598
  this.logger = logger.child({ module: "agent", provider: CODEX_PROVIDER });
1597
1599
  if (config.modeId === undefined) {
1598
1600
  throw new Error("Codex agent requires modeId to be specified");
@@ -2115,6 +2117,7 @@ class CodexAppServerAgentSession {
2115
2117
  ? "cancel"
2116
2118
  : "decline");
2117
2119
  pending.resolve({ decision });
2120
+ this.scheduleDeferredTurnCompletionAfterPermissionResponse();
2118
2121
  return;
2119
2122
  }
2120
2123
  if (pending.kind === "file") {
@@ -2125,14 +2128,17 @@ class CodexAppServerAgentSession {
2125
2128
  ? "cancel"
2126
2129
  : "decline");
2127
2130
  pending.resolve({ decision });
2131
+ this.scheduleDeferredTurnCompletionAfterPermissionResponse();
2128
2132
  return;
2129
2133
  }
2130
2134
  if (pending.kind === "permissions") {
2131
2135
  pending.resolve(buildCodexPermissionsResponse(response, pending.permissions));
2136
+ this.scheduleDeferredTurnCompletionAfterPermissionResponse();
2132
2137
  return;
2133
2138
  }
2134
2139
  if (pending.kind === "elicitation") {
2135
2140
  pending.resolve(buildCodexElicitationResponse(response));
2141
+ this.scheduleDeferredTurnCompletionAfterPermissionResponse();
2136
2142
  return;
2137
2143
  }
2138
2144
  // tool/requestUserInput
@@ -2161,6 +2167,7 @@ class CodexAppServerAgentSession {
2161
2167
  answers["default"] = { answers: fallbackAnswers && fallbackAnswers.length > 0 ? fallbackAnswers : [decision] };
2162
2168
  }
2163
2169
  pending.resolve({ answers });
2170
+ this.scheduleDeferredTurnCompletionAfterPermissionResponse();
2164
2171
  }
2165
2172
  describePersistence() {
2166
2173
  if (!this.currentThreadId)
@@ -2204,6 +2211,7 @@ class CodexAppServerAgentSession {
2204
2211
  this.pendingPermissionHandlers.clear();
2205
2212
  this.pendingPermissions.clear();
2206
2213
  this.resolvedPermissionRequests.clear();
2214
+ this.clearDeferredTurnCompletion();
2207
2215
  this.eventQueue?.end();
2208
2216
  this.eventQueue = null;
2209
2217
  if (this.client) {
@@ -2328,7 +2336,84 @@ class CodexAppServerAgentSession {
2328
2336
  emitEvent(event) {
2329
2337
  this.eventQueue?.push(event);
2330
2338
  }
2339
+ hasOutstandingPermissionRequests() {
2340
+ return this.pendingPermissionHandlers.size > 0;
2341
+ }
2342
+ clearDeferredTurnCompletion() {
2343
+ if (this.deferredTurnCompletion?.timer) {
2344
+ clearTimeout(this.deferredTurnCompletion.timer);
2345
+ }
2346
+ this.deferredTurnCompletion = null;
2347
+ }
2348
+ scheduleDeferredTurnCompletion(parsed, options) {
2349
+ const waitingOnPermission = options?.waitingOnPermission ??
2350
+ this.deferredTurnCompletion?.waitingOnPermission ??
2351
+ this.hasOutstandingPermissionRequests();
2352
+ this.clearDeferredTurnCompletion();
2353
+ this.turnCompletionInFlight = true;
2354
+ const deferred = {
2355
+ parsed,
2356
+ turnId: this.currentTurnId,
2357
+ waitingOnPermission,
2358
+ timer: null,
2359
+ };
2360
+ deferred.timer = setTimeout(() => {
2361
+ void this.flushDeferredTurnCompletion(deferred.turnId);
2362
+ }, TURN_COMPLETION_SETTLE_DELAY_MS);
2363
+ this.deferredTurnCompletion = deferred;
2364
+ }
2365
+ markDeferredTurnCompletionAwaitingPermissions() {
2366
+ if (!this.deferredTurnCompletion) {
2367
+ return;
2368
+ }
2369
+ this.scheduleDeferredTurnCompletion(this.deferredTurnCompletion.parsed, {
2370
+ waitingOnPermission: true,
2371
+ });
2372
+ }
2373
+ discardDeferredTurnCompletion(reason) {
2374
+ const deferred = this.deferredTurnCompletion;
2375
+ if (!deferred) {
2376
+ return;
2377
+ }
2378
+ this.logger.trace({
2379
+ reason,
2380
+ threadId: this.currentThreadId,
2381
+ turnId: deferred.turnId,
2382
+ }, "Discarding deferred Codex turn completion after resumed activity");
2383
+ this.clearDeferredTurnCompletion();
2384
+ this.turnCompletionInFlight = false;
2385
+ }
2386
+ noteTurnActivityAfterPermission(activity) {
2387
+ const deferred = this.deferredTurnCompletion;
2388
+ if (!deferred || !deferred.waitingOnPermission || this.hasOutstandingPermissionRequests()) {
2389
+ return;
2390
+ }
2391
+ this.discardDeferredTurnCompletion(activity);
2392
+ }
2393
+ async flushDeferredTurnCompletion(turnId) {
2394
+ const deferred = this.deferredTurnCompletion;
2395
+ if (!deferred || deferred.turnId !== turnId) {
2396
+ return;
2397
+ }
2398
+ if (this.hasOutstandingPermissionRequests()) {
2399
+ this.scheduleDeferredTurnCompletion(deferred.parsed, {
2400
+ waitingOnPermission: true,
2401
+ });
2402
+ return;
2403
+ }
2404
+ this.clearDeferredTurnCompletion();
2405
+ await this.finalizeTurnCompletion(deferred.parsed);
2406
+ }
2407
+ scheduleDeferredTurnCompletionAfterPermissionResponse() {
2408
+ if (!this.deferredTurnCompletion || this.hasOutstandingPermissionRequests()) {
2409
+ return;
2410
+ }
2411
+ this.scheduleDeferredTurnCompletion(this.deferredTurnCompletion.parsed, {
2412
+ waitingOnPermission: true,
2413
+ });
2414
+ }
2331
2415
  clearTurnState() {
2416
+ this.clearDeferredTurnCompletion();
2332
2417
  this.currentTurnId = null;
2333
2418
  this.emittedItemStartedIds.clear();
2334
2419
  this.emittedItemCompletedIds.clear();
@@ -2506,7 +2591,24 @@ class CodexAppServerAgentSession {
2506
2591
  return;
2507
2592
  }
2508
2593
  if (parsed.kind === "turn_completed") {
2594
+ // A later failed/interrupted terminal status must replace a deferred
2595
+ // "completed" status so we do not hide a real terminal error behind
2596
+ // the settle window.
2509
2597
  if (this.turnCompletionInFlight) {
2598
+ if (parsed.status === "completed") {
2599
+ this.scheduleDeferredTurnCompletion(parsed);
2600
+ return;
2601
+ }
2602
+ if (this.deferredTurnCompletion) {
2603
+ this.clearDeferredTurnCompletion();
2604
+ this.turnCompletionInFlight = false;
2605
+ }
2606
+ else {
2607
+ return;
2608
+ }
2609
+ }
2610
+ if (parsed.status === "completed") {
2611
+ this.scheduleDeferredTurnCompletion(parsed);
2510
2612
  return;
2511
2613
  }
2512
2614
  this.turnCompletionInFlight = true;
@@ -2514,6 +2616,7 @@ class CodexAppServerAgentSession {
2514
2616
  return;
2515
2617
  }
2516
2618
  if (parsed.kind === "plan_updated") {
2619
+ this.noteTurnActivityAfterPermission("plan_updated");
2517
2620
  const items = planStepsToTodoItems(parsed.plan.map((entry) => ({
2518
2621
  step: entry.step ?? "",
2519
2622
  status: entry.status ?? "pending",
@@ -2526,6 +2629,7 @@ class CodexAppServerAgentSession {
2526
2629
  return;
2527
2630
  }
2528
2631
  if (parsed.kind === "plan_delta") {
2632
+ this.noteTurnActivityAfterPermission("plan_delta");
2529
2633
  const previous = this.pendingPlanTexts.get(parsed.itemId) ?? "";
2530
2634
  const next = previous + parsed.delta;
2531
2635
  this.pendingPlanTexts.set(parsed.itemId, next);
@@ -2567,25 +2671,30 @@ class CodexAppServerAgentSession {
2567
2671
  return;
2568
2672
  }
2569
2673
  if (parsed.kind === "agent_message_delta") {
2674
+ this.noteTurnActivityAfterPermission("agent_message_delta");
2570
2675
  const prev = this.pendingAgentMessages.get(parsed.itemId) ?? "";
2571
2676
  this.pendingAgentMessages.set(parsed.itemId, prev + parsed.delta);
2572
2677
  return;
2573
2678
  }
2574
2679
  if (parsed.kind === "reasoning_delta") {
2680
+ this.noteTurnActivityAfterPermission("reasoning_delta");
2575
2681
  const prev = this.pendingReasoning.get(parsed.itemId) ?? [];
2576
2682
  prev.push(parsed.delta);
2577
2683
  this.pendingReasoning.set(parsed.itemId, prev);
2578
2684
  return;
2579
2685
  }
2580
2686
  if (parsed.kind === "exec_command_output_delta") {
2687
+ this.noteTurnActivityAfterPermission("exec_command_output_delta");
2581
2688
  this.appendOutputDeltaChunk(this.pendingCommandOutputDeltas, parsed.callId, parsed.chunk, { decodeBase64: true });
2582
2689
  return;
2583
2690
  }
2584
2691
  if (parsed.kind === "file_change_output_delta") {
2692
+ this.noteTurnActivityAfterPermission("file_change_output_delta");
2585
2693
  this.appendOutputDeltaChunk(this.pendingFileChangeOutputDeltas, parsed.itemId, parsed.delta);
2586
2694
  return;
2587
2695
  }
2588
2696
  if (parsed.kind === "exec_command_started") {
2697
+ this.noteTurnActivityAfterPermission("exec_command_started");
2589
2698
  if (parsed.callId) {
2590
2699
  this.emittedExecCommandStartedCallIds.add(parsed.callId);
2591
2700
  this.pendingCommandOutputDeltas.delete(parsed.callId);
@@ -2602,6 +2711,7 @@ class CodexAppServerAgentSession {
2602
2711
  return;
2603
2712
  }
2604
2713
  if (parsed.kind === "exec_command_completed") {
2714
+ this.noteTurnActivityAfterPermission("exec_command_completed");
2605
2715
  const bufferedOutput = this.consumeOutputDelta(this.pendingCommandOutputDeltas, parsed.callId);
2606
2716
  const timelineItem = mapCodexExecNotificationToToolCall({
2607
2717
  callId: parsed.callId,
@@ -2620,6 +2730,7 @@ class CodexAppServerAgentSession {
2620
2730
  return;
2621
2731
  }
2622
2732
  if (parsed.kind === "patch_apply_started") {
2733
+ this.noteTurnActivityAfterPermission("patch_apply_started");
2623
2734
  if (parsed.callId) {
2624
2735
  this.pendingFileChangeOutputDeltas.delete(parsed.callId);
2625
2736
  }
@@ -2639,6 +2750,7 @@ class CodexAppServerAgentSession {
2639
2750
  return;
2640
2751
  }
2641
2752
  if (parsed.kind === "patch_apply_completed") {
2753
+ this.noteTurnActivityAfterPermission("patch_apply_completed");
2642
2754
  const bufferedOutput = this.consumeOutputDelta(this.pendingFileChangeOutputDeltas, parsed.callId);
2643
2755
  const timelineItem = mapCodexPatchNotificationToToolCall({
2644
2756
  callId: parsed.callId,
@@ -2660,6 +2772,7 @@ class CodexAppServerAgentSession {
2660
2772
  return;
2661
2773
  }
2662
2774
  if (parsed.kind === "item_completed") {
2775
+ this.noteTurnActivityAfterPermission("item_completed");
2663
2776
  // Codex emits mirrored lifecycle notifications via both `codex/event/item_*`
2664
2777
  // and canonical `item/*`. We render only the canonical channel to avoid
2665
2778
  // duplicated assistant/reasoning rows.
@@ -2670,6 +2783,7 @@ class CodexAppServerAgentSession {
2670
2783
  return;
2671
2784
  }
2672
2785
  if (parsed.kind === "item_started") {
2786
+ this.noteTurnActivityAfterPermission("item_started");
2673
2787
  if (parsed.source === "codex_event") {
2674
2788
  return;
2675
2789
  }
@@ -2775,6 +2889,7 @@ class CodexAppServerAgentSession {
2775
2889
  : parsed.networkApprovalContext
2776
2890
  ? "Allow network access"
2777
2891
  : "Run command";
2892
+ this.markDeferredTurnCompletionAwaitingPermissions();
2778
2893
  const request = {
2779
2894
  id: requestId,
2780
2895
  provider: CODEX_PROVIDER,
@@ -2818,6 +2933,7 @@ class CodexAppServerAgentSession {
2818
2933
  handleFileChangeApprovalRequest(requestMethod, params, rawRequestId) {
2819
2934
  const parsed = params;
2820
2935
  const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
2936
+ this.markDeferredTurnCompletionAwaitingPermissions();
2821
2937
  const request = {
2822
2938
  id: requestId,
2823
2939
  provider: CODEX_PROVIDER,
@@ -2851,6 +2967,7 @@ class CodexAppServerAgentSession {
2851
2967
  const parsed = params;
2852
2968
  const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
2853
2969
  const questions = normalizeCodexQuestionDescriptors(parsed.questions);
2970
+ this.markDeferredTurnCompletionAwaitingPermissions();
2854
2971
  const request = {
2855
2972
  id: requestId,
2856
2973
  provider: CODEX_PROVIDER,
@@ -2889,6 +3006,7 @@ class CodexAppServerAgentSession {
2889
3006
  handlePermissionsApprovalRequest(requestMethod, params, rawRequestId) {
2890
3007
  const parsed = params;
2891
3008
  const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
3009
+ this.markDeferredTurnCompletionAwaitingPermissions();
2892
3010
  const request = {
2893
3011
  id: requestId,
2894
3012
  provider: CODEX_PROVIDER,
@@ -2936,6 +3054,7 @@ class CodexAppServerAgentSession {
2936
3054
  : parsed.serverName
2937
3055
  ? `Respond to ${parsed.serverName}`
2938
3056
  : "Respond to MCP server";
3057
+ this.markDeferredTurnCompletionAwaitingPermissions();
2939
3058
  const request = {
2940
3059
  id: requestId,
2941
3060
  provider: CODEX_PROVIDER,
@@ -2985,6 +3104,12 @@ class CodexAppServerAgentSession {
2985
3104
  });
2986
3105
  }
2987
3106
  }
3107
+ Object.assign(__codexAppServerInternals, {
3108
+ TURN_COMPLETION_SETTLE_DELAY_MS,
3109
+ createTestSession(config, resumeHandle, logger, spawnAppServer) {
3110
+ return new CodexAppServerAgentSession(config, resumeHandle, logger, spawnAppServer);
3111
+ },
3112
+ });
2988
3113
  export class CodexAppServerAgentClient {
2989
3114
  constructor(logger, runtimeSettings) {
2990
3115
  this.logger = logger;