@seawork/server 1.0.20 → 1.0.21-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/server/client/daemon-client.d.ts +7 -1
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +26 -0
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +9 -3
  6. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  7. package/dist/server/server/agent/providers/codex-app-server-agent.js +83 -5
  8. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  9. package/dist/server/server/bootstrap.d.ts.map +1 -1
  10. package/dist/server/server/bootstrap.js +4 -0
  11. package/dist/server/server/bootstrap.js.map +1 -1
  12. package/dist/server/server/pr-review-watcher.d.ts +24 -0
  13. package/dist/server/server/pr-review-watcher.d.ts.map +1 -0
  14. package/dist/server/server/pr-review-watcher.js +281 -0
  15. package/dist/server/server/pr-review-watcher.js.map +1 -0
  16. package/dist/server/server/session.d.ts +4 -0
  17. package/dist/server/server/session.d.ts.map +1 -1
  18. package/dist/server/server/session.js +117 -1
  19. package/dist/server/server/session.js.map +1 -1
  20. package/dist/server/server/workspace-ignore/store.d.ts +17 -0
  21. package/dist/server/server/workspace-ignore/store.d.ts.map +1 -0
  22. package/dist/server/server/workspace-ignore/store.js +92 -0
  23. package/dist/server/server/workspace-ignore/store.js.map +1 -0
  24. package/dist/server/shared/messages.d.ts +584 -0
  25. package/dist/server/shared/messages.d.ts.map +1 -1
  26. package/dist/server/shared/messages.js +49 -0
  27. package/dist/server/shared/messages.js.map +1 -1
  28. package/dist/server/utils/directory-suggestions.d.ts +2 -0
  29. package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
  30. package/dist/server/utils/directory-suggestions.js +81 -11
  31. package/dist/server/utils/directory-suggestions.js.map +1 -1
  32. package/package.json +3 -3
@@ -20,6 +20,12 @@ import { buildCodexFeatures, codexModelSupportsFastMode } from "./codex-feature-
20
20
  import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, resolveBinaryVersion, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
21
21
  const DEFAULT_TIMEOUT_MS = 14 * 24 * 60 * 60 * 1000;
22
22
  const TURN_START_TIMEOUT_MS = 90 * 1000;
23
+ // issue #259: window after turn/interrupt during which the next turn/start
24
+ // gets a sentinel reminder prepended to its input.
25
+ const CANCEL_REMINDER_WINDOW_MS = 60 * 1000;
26
+ const CANCEL_REMINDER_TEXT = "[seawork system note] The user canceled the previous turn before it finished. " +
27
+ "Ignore any prior user message that did not receive a complete answer. " +
28
+ "Only respond to the message below.";
23
29
  const CODEX_EXTERNAL_MIGRATION_MIN_VERSION = "0.128.0";
24
30
  const CODEX_PROVIDER = "codex";
25
31
  const CODEX_SEAWORK_PROVIDER_ID = "seawork";
@@ -2092,6 +2098,10 @@ class CodexAppServerAgentSession {
2092
2098
  this.capabilities = CODEX_APP_SERVER_CAPABILITIES;
2093
2099
  this.currentThreadId = null;
2094
2100
  this.currentTurnId = null;
2101
+ // issue #259: codex thread is append-only and turn/interrupt does not retract
2102
+ // the canceled user message. Track when interrupt completed so the next
2103
+ // turn/start can warn the model to ignore the prior prompt.
2104
+ this.lastCanceledAt = null;
2095
2105
  this.client = null;
2096
2106
  this.subscribers = new Set();
2097
2107
  this.nextTurnOrdinal = 0;
@@ -2280,8 +2290,29 @@ class CodexAppServerAgentSession {
2280
2290
  }
2281
2291
  this.planModeEnabled = value;
2282
2292
  this.refreshResolvedCollaborationMode();
2293
+ // collaborationMode switches may bring in a different settings.model
2294
+ // (when config.model is unset and plan/code modes carry different model
2295
+ // hints). Mirror setModel()'s fast-tier guard so we don't ship
2296
+ // serviceTier:"fast" to a model that doesn't support it.
2297
+ if (this.serviceTier && !codexModelSupportsFastMode(this.effectiveTurnModel())) {
2298
+ // Clear both runtime and persisted state — the constructor restores
2299
+ // serviceTier from config.featureValues.fast_mode on session reload,
2300
+ // so leaving the persisted flag true would bring the invalid state back.
2301
+ this.serviceTier = null;
2302
+ this.config.featureValues = {
2303
+ ...(this.config.featureValues ?? {}),
2304
+ fast_mode: false,
2305
+ };
2306
+ }
2283
2307
  this.cachedRuntimeInfo = null;
2284
2308
  }
2309
+ effectiveTurnModel() {
2310
+ const collabModel = this.resolvedCollaborationMode?.settings?.model;
2311
+ if (typeof collabModel === "string" && collabModel.length > 0) {
2312
+ return collabModel;
2313
+ }
2314
+ return this.config.model;
2315
+ }
2285
2316
  rememberPlanResult(item) {
2286
2317
  if (item.detail.type !== "plan") {
2287
2318
  return;
@@ -2317,14 +2348,16 @@ class CodexAppServerAgentSession {
2317
2348
  this.emitEvent({ type: "permission_requested", provider: CODEX_PROVIDER, request });
2318
2349
  }
2319
2350
  /**
2320
- * Prepare the session for plan implementation by disabling plan/fast mode
2321
- * and returning the implementation prompt. The caller is responsible for
2322
- * starting the turn through the normal streamAgent path.
2351
+ * Prepare the session for plan implementation by disabling plan mode (so the
2352
+ * next turn runs in `code` collaboration mode instead of generating another
2353
+ * plan) and returning the implementation prompt. fast_mode is intentionally
2354
+ * preserved — it only controls the inference tier and is orthogonal to the
2355
+ * plan→implement transition. The caller is responsible for starting the
2356
+ * turn through the normal streamAgent path.
2323
2357
  */
2324
2358
  preparePlanImplementation(params) {
2325
2359
  const planText = typeof params.planText === "string" ? normalizePlanMarkdown(params.planText) : "";
2326
2360
  this.applyFeatureValue("plan_mode", false);
2327
- this.applyFeatureValue("fast_mode", false);
2328
2361
  return buildCodexPlanImplementationPrompt(planText);
2329
2362
  }
2330
2363
  registerRequestHandlers() {
@@ -2558,7 +2591,7 @@ class CodexAppServerAgentSession {
2558
2591
  else {
2559
2592
  await this.ensureThread();
2560
2593
  }
2561
- const input = await this.buildUserInput(effectivePrompt);
2594
+ const input = this.maybePrependCancelReminder(await this.buildUserInput(effectivePrompt));
2562
2595
  const preset = MODE_PRESETS[this.currentMode] ?? MODE_PRESETS[DEFAULT_CODEX_MODE_ID];
2563
2596
  const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
2564
2597
  const sandboxPolicyType = this.config.sandboxMode ?? preset.sandbox;
@@ -2612,6 +2645,11 @@ class CodexAppServerAgentSession {
2612
2645
  throw error;
2613
2646
  }
2614
2647
  this.logger.info({ turnId, elapsedMs: Date.now() - turnStartT0 }, "[timing] codex turn/start acknowledged");
2648
+ // issue #259: do NOT consume `lastCanceledAt` here. The sentinel is only
2649
+ // cleared once we see turn/completed with status "completed" — a
2650
+ // turn/start ack can still race with turn/completed: failed, in which
2651
+ // case the orphaned prompt is still in the thread and the next retry
2652
+ // must carry the reminder (PR #273 review #3).
2615
2653
  return { turnId };
2616
2654
  }
2617
2655
  subscribe(callback) {
@@ -2835,6 +2873,12 @@ class CodexAppServerAgentSession {
2835
2873
  threadId: this.currentThreadId,
2836
2874
  turnId: this.currentTurnId,
2837
2875
  });
2876
+ // issue #259: codex protocol has no thread-truncate RPC, so a canceled
2877
+ // user prompt stays in the thread. The sentinel timestamp is set on the
2878
+ // turn/completed `interrupted` event below (not here), because a
2879
+ // successful turn/interrupt RPC can race with normal completion — if
2880
+ // the model already finished, the prompt has a paired assistant reply
2881
+ // and the next turn must NOT prepend the sentinel.
2838
2882
  }
2839
2883
  catch (error) {
2840
2884
  this.logger.warn({ error }, "Failed to interrupt Codex turn");
@@ -2948,6 +2992,27 @@ class CodexAppServerAgentSession {
2948
2992
  const blocks = prompt;
2949
2993
  return await codexAppServerTurnInputFromPrompt(blocks, this.logger);
2950
2994
  }
2995
+ // issue #259: if the previous turn was canceled via turn/interrupt and the
2996
+ // user resends a prompt within the window, prepend a one-shot sentinel so
2997
+ // the model knows to ignore the orphaned user message that codex's
2998
+ // append-only thread cannot retract. Sentinel lifecycle is owned by
2999
+ // turn/completed: it is only cleared on a "completed" status. We do NOT
3000
+ // clear it here on a successful turn/start ack, because that turn could
3001
+ // still end as turn/completed: failed, in which case the orphaned prompt
3002
+ // remains in the thread and the next retry must still carry the reminder
3003
+ // (PR #273 review #3). The only side-effect here is eagerly dropping a
3004
+ // stale timestamp that fell outside the window — no turn/completed is
3005
+ // expected to arrive for it.
3006
+ maybePrependCancelReminder(input) {
3007
+ if (this.lastCanceledAt === null)
3008
+ return input;
3009
+ const elapsed = Date.now() - this.lastCanceledAt;
3010
+ if (elapsed >= CANCEL_REMINDER_WINDOW_MS) {
3011
+ this.lastCanceledAt = null;
3012
+ return input;
3013
+ }
3014
+ return [{ type: "text", text: CANCEL_REMINDER_TEXT }, ...input];
3015
+ }
2951
3016
  emitEvent(event) {
2952
3017
  if (event.type === "timeline") {
2953
3018
  if (event.item.type === "assistant_message") {
@@ -3004,9 +3069,22 @@ class CodexAppServerAgentSession {
3004
3069
  });
3005
3070
  }
3006
3071
  else if (parsed.status === "interrupted") {
3072
+ // issue #259: the user prompt for this turn is now orphaned in the
3073
+ // append-only thread. Record the moment so the next turn/start can
3074
+ // prepend a sentinel telling the model to ignore it.
3075
+ this.lastCanceledAt = Date.now();
3007
3076
  this.emitEvent({ type: "turn_canceled", provider: CODEX_PROVIDER, reason: "interrupted" });
3008
3077
  }
3009
3078
  else {
3079
+ // issue #259: a normal `completed` turn means the prior user prompt
3080
+ // has a paired assistant reply — clear any pending sentinel (e.g.
3081
+ // from a turn/interrupt that raced with normal completion) so we
3082
+ // don't tell the model to ignore valid history. Unknown future
3083
+ // statuses leave `lastCanceledAt` untouched: we don't know whether
3084
+ // the prompt was answered or orphaned, so we keep current state.
3085
+ if (parsed.status === "completed") {
3086
+ this.lastCanceledAt = null;
3087
+ }
3010
3088
  if (this.planModeEnabled && this.latestPlanResult?.text) {
3011
3089
  this.emitSyntheticPlanApprovalRequest(this.latestPlanResult.text);
3012
3090
  }