jeopi-cli 16.2.30 → 16.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.
@@ -222,7 +222,7 @@ import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
222
222
  import type { HookCommandContext } from "../extensibility/hooks/types";
223
223
  import type { Skill, SkillWarning } from "../extensibility/skills";
224
224
  import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
225
- import { GoalRuntime } from "../goals/runtime";
225
+ import { type GoalCompletionVerdict, GoalRuntime } from "../goals/runtime";
226
226
  import type { Goal, GoalModeState } from "../goals/state";
227
227
  import type { HindsightSessionState } from "../hindsight/state";
228
228
  import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
@@ -539,13 +539,19 @@ function refusalBackoffBudgetMs(): number {
539
539
  }
540
540
  /**
541
541
  * Budget for callers on the user-visible `/quit` / `/exit` shutdown path that
542
- * want to cap how long they wait for `MnemopiSessionState.dispose()` to finish
543
- * its consolidate pass. Consolidate fires fresh LLM fact extractions, each a
544
- * 1–3 s round-trip, so interactive shutdown passes this budget to keep the
545
- * UI responsive. Callers that keep the process/session host alive must omit it
546
- * so dispose still awaits the full consolidate-then-close pipeline.
542
+ * want to cap how long they wait for `MnemopiSessionState.dispose()` (and the
543
+ * Hindsight retain-queue flush, see the dispose() call site) to finish writing
544
+ * this session's memory before the process exits. Consolidate fires fresh LLM
545
+ * fact extractions, each a 1-3s round-trip; the retain-queue flush is a single
546
+ * HTTP POST. The prior 1500ms budget was tighter than a single extraction
547
+ * round-trip, so it lost the race on effectively every session with any
548
+ * memory-worthy content — "write before walking away" in name only. 8s still
549
+ * bounds worst-case UI unresponsiveness on /exit while giving a small
550
+ * consolidation batch (1-3 facts) a realistic chance to land before the
551
+ * process exits. Callers that keep the process/session host alive must omit
552
+ * it so dispose still awaits the full write pipeline unbounded.
547
553
  */
548
- export const SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1_500;
554
+ export const SHUTDOWN_CONSOLIDATE_BUDGET_MS = 8_000;
549
555
 
550
556
  export interface AgentSessionDisposeOptions {
551
557
  mnemopiConsolidateTimeoutMs?: number;
@@ -728,6 +734,15 @@ export interface AgentSessionConfig {
728
734
  rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<{ systemPrompt: string[] }>;
729
735
  /** Rebuild the SSH tool from current capability discovery results. */
730
736
  reloadSshTool?: () => Promise<AgentTool | null>;
737
+ /**
738
+ * Independent-verifier bridge for goal-mode completion (see goals/verifier.ts).
739
+ * Spawns the bundled `goal-verifier` sub-agent to grade a completion claim against
740
+ * the objective and the actual repo diff — a fresh session/context, no exposure to
741
+ * this session's transcript. Wired from sdk.ts (needs the constructed `toolSession`).
742
+ * Undefined when `goal.verifyCompletion` is off or the caller has no tool session
743
+ * (e.g. minimal SDK embedders) — GoalRuntime skips independent grading in that case.
744
+ */
745
+ verifyGoalCompletion?: (objective: string, evidence: string) => Promise<GoalCompletionVerdict>;
731
746
  requestedToolNames?: ReadonlySet<string>;
732
747
  /**
733
748
  * Optional accessor for live MCP server instructions. Read by the session's
@@ -2210,6 +2225,14 @@ export class AgentSession {
2210
2225
  { deliverAs: message.deliverAs },
2211
2226
  );
2212
2227
  },
2228
+ verifyCompletion: config.verifyGoalCompletion
2229
+ ? async (objective: string, evidence: string): Promise<GoalCompletionVerdict> => {
2230
+ if (!this.settings.get("goal.verifyCompletion")) {
2231
+ return { verdict: "okay", justification: "goal.verifyCompletion disabled", requiredFixes: [] };
2232
+ }
2233
+ return config.verifyGoalCompletion!(objective, evidence);
2234
+ }
2235
+ : undefined,
2213
2236
  });
2214
2237
  this.#cancelExitRecorder = postmortem.register(`agent-session:${this.sessionManager.getSessionId()}`, reason => {
2215
2238
  this.#recordSessionExit(reason);
@@ -5618,7 +5641,27 @@ export class AgentSession {
5618
5641
  // Reversed, the spliced batch survives just long enough to fail the
5619
5642
  // identity check and get dropped with a `session vanished` warning.
5620
5643
  const hindsightState = this.getHindsightSessionState();
5621
- await hindsightState?.flushRetainQueue();
5644
+ // Bounded when a shutdown budget is set (interactive /quit /exit): the
5645
+ // underlying HindsightApi client makes plain `fetch()` calls with no
5646
+ // timeout/signal, so an unreachable or hung Hindsight server would
5647
+ // otherwise block process exit indefinitely. Callers that keep the
5648
+ // process/session host alive (no timeout passed) still await the flush
5649
+ // unbounded, matching the mnemopi dispose contract below.
5650
+ if (hindsightState) {
5651
+ const { mnemopiConsolidateTimeoutMs: timeoutMs } = options;
5652
+ if (timeoutMs !== undefined && timeoutMs > 0) {
5653
+ try {
5654
+ await withTimeout(hindsightState.flushRetainQueue(), timeoutMs, "Hindsight retain-queue flush");
5655
+ } catch (error) {
5656
+ logger.warn("Hindsight retain-queue flush exceeded shutdown budget; remaining items dropped", {
5657
+ timeoutMs,
5658
+ error: String(error),
5659
+ });
5660
+ }
5661
+ } else {
5662
+ await hindsightState.flushRetainQueue();
5663
+ }
5664
+ }
5622
5665
  this.setHindsightSessionState(undefined);
5623
5666
  hindsightState?.dispose();
5624
5667
  const mnemopiState = setMnemopiSessionState(this, undefined);
@@ -12,6 +12,7 @@ import designerMd from "../prompts/agents/designer.md" with { type: "text" };
12
12
  import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
13
13
  // Embed agent markdown files at build time
14
14
  import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
15
+ import goalVerifierMd from "../prompts/agents/goal-verifier.md" with { type: "text" };
15
16
  import librarianMd from "../prompts/agents/librarian.md" with { type: "text" };
16
17
 
17
18
  import planMd from "../prompts/agents/plan.md" with { type: "text" };
@@ -47,6 +48,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
47
48
  { fileName: "explore.md", template: exploreMd },
48
49
  { fileName: "plan.md", template: planMd },
49
50
  { fileName: "critic.md", template: criticMd },
51
+ { fileName: "goal-verifier.md", template: goalVerifierMd },
50
52
  { fileName: "architect.md", template: architectMd },
51
53
  { fileName: "designer.md", template: designerMd },
52
54
  { fileName: "reviewer.md", template: reviewerMd },