omnius 1.0.688 → 1.0.689

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.
package/dist/index.js CHANGED
@@ -663967,7 +663967,7 @@ ${loadPrompt("agentic/system-small.md")}`;
663967
663967
  if (this.options.subAgent || this.options.recursionDepth > 0)
663968
663968
  return [];
663969
663969
  const normalizedGoal = goal.replace(/\s+/g, " ").trim();
663970
- if (normalizedGoal.length < 120 && !this._isContinuationResumeGoal(normalizedGoal))
663970
+ if (normalizedGoal.length < 120 && !this._resumeInterrupted)
663971
663971
  return [];
663972
663972
  return [
663973
663973
  {
@@ -664035,44 +664035,6 @@ ${loadPrompt("agentic/system-small.md")}`;
664035
664035
  }
664036
664036
  ];
664037
664037
  }
664038
- _isContinuationResumeGoal(goal) {
664039
- const tokens3 = goal.toLowerCase().replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter(Boolean);
664040
- if (tokens3.length === 0 || tokens3.length > 14)
664041
- return false;
664042
- const continuationTerms = /* @__PURE__ */ new Set([
664043
- "again",
664044
- "and",
664045
- "back",
664046
- "carry",
664047
- "complete",
664048
- "continue",
664049
- "finish",
664050
- "from",
664051
- "last",
664052
- "latest",
664053
- "left",
664054
- "my",
664055
- "off",
664056
- "please",
664057
- "previous",
664058
- "prior",
664059
- "proceed",
664060
- "request",
664061
- "resume",
664062
- "task",
664063
- "that",
664064
- "the",
664065
- "this",
664066
- "to",
664067
- "try",
664068
- "work"
664069
- ]);
664070
- const hasResumeVerb = tokens3.some((token) => token === "continue" || token === "resume" || token === "proceed" || token === "complete" || token === "finish");
664071
- if (!hasResumeVerb)
664072
- return false;
664073
- const continuationTokenCount = tokens3.filter((token) => continuationTerms.has(token)).length;
664074
- return continuationTokenCount / tokens3.length >= 0.7;
664075
- }
664076
664038
  /**
664077
664039
  * Build a compact workboard context string for injection into the
664078
664040
  * system prompt. Returns null when no active board exists or when
@@ -677190,7 +677152,7 @@ Respond with the assessment and take the selected evidence-backed action.`;
677190
677152
  this._memexArchive.clear();
677191
677153
  this._todoOutputLedger.clear();
677192
677154
  setTodoSessionId(this._sessionId);
677193
- const continuesPriorTask = this._allowImplicitContinuation || this._isContinuationResumeGoal(persistentTaskGoal);
677155
+ const continuesPriorTask = this._allowImplicitContinuation || this._resumeInterrupted;
677194
677156
  if (!continuesPriorTask) {
677195
677157
  this._hidePriorSessionTodosForFreshTask(persistentTaskGoal);
677196
677158
  }
@@ -731239,6 +731201,7 @@ __export(omnius_directory_exports, {
731239
731201
  appendCompactionAudit: () => appendCompactionAudit,
731240
731202
  buildContextRestorePrompt: () => buildContextRestorePrompt,
731241
731203
  buildContextRestoreSnapshot: () => buildContextRestoreSnapshot,
731204
+ buildExactContinuationSnapshot: () => buildExactContinuationSnapshot,
731242
731205
  buildHandoffPrompt: () => buildHandoffPrompt,
731243
731206
  cleanPromptForDiary: () => cleanPromptForDiary,
731244
731207
  clearTaskHandoff: () => clearTaskHandoff,
@@ -731260,6 +731223,8 @@ __export(omnius_directory_exports, {
731260
731223
  loadSessionHistory: () => loadSessionHistory,
731261
731224
  loadTuiSessionState: () => loadTuiSessionState,
731262
731225
  loadUsageHistory: () => loadUsageHistory,
731226
+ pendingTaskMatchesInterruptionIdentity: () => pendingTaskMatchesInterruptionIdentity,
731227
+ pendingTaskMatchesRunningInterruption: () => pendingTaskMatchesRunningInterruption,
731263
731228
  readIndexData: () => readIndexData,
731264
731229
  readIndexMeta: () => readIndexMeta,
731265
731230
  readTaskHandoff: () => readTaskHandoff2,
@@ -731659,21 +731624,64 @@ function loadRecentSessions(repoRoot, limit2 = 5) {
731659
731624
  return [];
731660
731625
  }
731661
731626
  }
731627
+ function isValidPendingTask(value2, repoRoot) {
731628
+ if (!value2 || typeof value2 !== "object") return false;
731629
+ const task = value2;
731630
+ if (typeof task.prompt !== "string" || !task.prompt.trim() || typeof task.progressSummary !== "string" || !Array.isArray(task.filesModified) || !task.filesModified.every((item) => typeof item === "string") || typeof task.bruteForce !== "boolean" || typeof task.savedAt !== "string" || !Number.isFinite(Date.parse(task.savedAt)) || typeof task.toolCallCount !== "number" || !Number.isFinite(task.toolCallCount) || task.toolCallCount < 0) {
731631
+ return false;
731632
+ }
731633
+ if (task.schema !== void 0 && task.schema !== "omnius.pending-task.v2") {
731634
+ return false;
731635
+ }
731636
+ if (task.projectRoot !== void 0 && resolve77(task.projectRoot) !== resolve77(repoRoot)) {
731637
+ return false;
731638
+ }
731639
+ if (task.interruption !== void 0) {
731640
+ const interruption = task.interruption;
731641
+ if (typeof interruption.sessionId !== "string" || !interruption.sessionId.trim() || typeof interruption.runId !== "string" || !interruption.runId.trim() || !Number.isInteger(interruption.taskEpoch) || interruption.taskEpoch < 1 || !Number.isInteger(interruption.ownerGeneration) || interruption.ownerGeneration < 1 || interruption.phase !== "pausing" && interruption.phase !== "paused") {
731642
+ return false;
731643
+ }
731644
+ }
731645
+ if (task.resumeOnStartup === true && !task.interruption) return false;
731646
+ return true;
731647
+ }
731648
+ function pendingTaskMatchesRunningInterruption(pendingTask, candidate) {
731649
+ return candidate?.phase === "running" && pendingTaskMatchesInterruptionIdentity(pendingTask, candidate);
731650
+ }
731651
+ function pendingTaskMatchesInterruptionIdentity(pendingTask, candidate) {
731652
+ const expected = pendingTask.interruption;
731653
+ return Boolean(
731654
+ expected && candidate && candidate.sessionId === expected.sessionId && candidate.runId === expected.runId && candidate.taskEpoch === expected.taskEpoch && candidate.ownerGeneration === expected.ownerGeneration
731655
+ );
731656
+ }
731662
731657
  function savePendingTask(repoRoot, task) {
731663
731658
  const historyDir = join162(repoRoot, OMNIUS_DIR2, "history");
731664
731659
  mkdirSync95(historyDir, { recursive: true });
731665
- writeFileSync85(
731666
- join162(historyDir, PENDING_TASK_FILE),
731667
- JSON.stringify(task, null, 2) + "\n",
731668
- "utf-8"
731669
- );
731660
+ const filePath = join162(historyDir, PENDING_TASK_FILE);
731661
+ const tempPath = `${filePath}.tmp-${process.pid}`;
731662
+ const checkpoint = {
731663
+ ...task,
731664
+ schema: "omnius.pending-task.v2",
731665
+ projectRoot: resolve77(repoRoot)
731666
+ };
731667
+ const payload = JSON.stringify(checkpoint, null, 2) + "\n";
731668
+ writeFileSync85(tempPath, payload, "utf-8");
731669
+ try {
731670
+ renameSync30(tempPath, filePath);
731671
+ } catch {
731672
+ writeFileSync85(filePath, payload, "utf-8");
731673
+ try {
731674
+ unlinkSync37(tempPath);
731675
+ } catch {
731676
+ }
731677
+ }
731670
731678
  }
731671
731679
  function loadPendingTask(repoRoot) {
731672
731680
  const filePath = join162(repoRoot, OMNIUS_DIR2, "history", PENDING_TASK_FILE);
731673
731681
  try {
731674
731682
  if (!existsSync148(filePath)) return null;
731675
731683
  const data = JSON.parse(readFileSync124(filePath, "utf-8"));
731676
- return data;
731684
+ return isValidPendingTask(data, repoRoot) ? data : null;
731677
731685
  } catch {
731678
731686
  return null;
731679
731687
  }
@@ -732047,6 +732055,13 @@ function saveSessionContext(repoRoot, entry) {
732047
732055
  ctx3.entries.push(normalizedEntry);
732048
732056
  }
732049
732057
  }
732058
+ ctx3.entries = ctx3.entries.map((item, index) => ({ item, index })).sort((a2, b) => {
732059
+ const left = Date.parse(a2.item.savedAt || "");
732060
+ const right = Date.parse(b.item.savedAt || "");
732061
+ const leftTime = Number.isFinite(left) ? left : 0;
732062
+ const rightTime = Number.isFinite(right) ? right : 0;
732063
+ return leftTime - rightTime || a2.index - b.index;
732064
+ }).map(({ item }) => item);
732050
732065
  if (ctx3.entries.length > ctx3.maxEntries) {
732051
732066
  ctx3.entries = ctx3.entries.slice(-ctx3.maxEntries);
732052
732067
  }
@@ -732180,80 +732195,12 @@ function isManualSessionEntry(entry) {
732180
732195
  const summary = normalizeSessionText(entry.summary || entry.assistantResponse, 160).toLowerCase();
732181
732196
  return entry.source === "manual" || task === "(manual save)" || entry.toolCalls === 0 && summary.startsWith("manual context save");
732182
732197
  }
732183
- function isDeicticContinuationGoal(goal) {
732184
- const tokens3 = normalizeSessionText(cleanPromptForDiary(goal), 180).toLowerCase().replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter(Boolean);
732185
- if (tokens3.length === 0 || tokens3.length > 14) return false;
732186
- const hasResumeVerb = tokens3.some(
732187
- (token) => token === "continue" || token === "resume" || token === "proceed" || token === "complete" || token === "finish"
732188
- );
732189
- if (!hasResumeVerb) return false;
732190
- const deicticCount = tokens3.filter((token) => DEICTIC_CONTINUATION_TERMS.has(token)).length;
732191
- return deicticCount / tokens3.length >= 0.7;
732192
- }
732193
732198
  function readRestoreWorkboard(repoRoot, runId) {
732194
732199
  if (!runId) return null;
732195
732200
  return readJsonOrNull(
732196
732201
  join162(repoRoot, OMNIUS_DIR2, "workboards", runId, "active.json")
732197
732202
  );
732198
732203
  }
732199
- function scoreRestoreLedger(ledger, workboard) {
732200
- const status = ledger.status || "open";
732201
- if (status === "approved") return -1e3;
732202
- const evidence = ledger.evidence ?? [];
732203
- const unresolvedCount = ledger.unresolved?.length ?? 0;
732204
- const activeCards = (workboard?.cards ?? []).filter(
732205
- (card) => card.status === "open" || card.status === "in_progress" || card.status === "needs_changes" || card.status === "blocked"
732206
- ).length;
732207
- const mutationTools = /* @__PURE__ */ new Set(["file_edit", "file_patch", "batch_edit", "file_write"]);
732208
- const mutationEvidence = evidence.filter((item) => mutationTools.has(item.toolName || "")).length;
732209
- const failedMutationEvidence = evidence.filter(
732210
- (item) => mutationTools.has(item.toolName || "") && item.success === false
732211
- ).length;
732212
- const targetPathEvidence = evidence.filter((item) => (item.targetPaths?.length ?? 0) > 0).length;
732213
- const blockedEvidence = evidence.filter((item) => {
732214
- const summary = String(item.summary ?? "").toLowerCase();
732215
- return summary.includes("[focus supervisor block]") || summary.includes("stale") || summary.includes("blocked");
732216
- }).length;
732217
- let score = 0;
732218
- if (status === "open") score += 10;
732219
- else if (status === "incomplete_verification") score += 18;
732220
- else if (status === "request_changes" || status === "blocked") score += 16;
732221
- else score += 4;
732222
- score += Math.min(evidence.length, 20);
732223
- score += Math.min(unresolvedCount, 10) * 4;
732224
- score += Math.min(activeCards, 8) * 3;
732225
- score += Math.min(mutationEvidence, 8) * 5;
732226
- score += Math.min(failedMutationEvidence, 8) * 3;
732227
- score += Math.min(targetPathEvidence, 6) * 2;
732228
- score += Math.min(blockedEvidence, 6) * 2;
732229
- const goal = normalizeSessionText(ledger.goal, 260);
732230
- if (goal.length >= 80) score += 5;
732231
- if (isDeicticContinuationGoal(goal) && mutationEvidence === 0 && unresolvedCount === 0 && activeCards === 0) {
732232
- score -= 25;
732233
- }
732234
- return score;
732235
- }
732236
- function selectActiveTaskAnchor(repoRoot) {
732237
- const ledgerDir = join162(repoRoot, OMNIUS_DIR2, "completion-ledgers");
732238
- try {
732239
- if (!existsSync148(ledgerDir)) return null;
732240
- const candidates = readdirSync48(ledgerDir).filter((name10) => name10.endsWith(".json")).map((name10) => {
732241
- const filePath = join162(ledgerDir, name10);
732242
- const ledger = readJsonOrNull(filePath);
732243
- if (!ledger || !ledger.goal) return null;
732244
- const runId = ledger.runId || name10.replace(/\.json$/, "");
732245
- const workboard = readRestoreWorkboard(repoRoot, runId);
732246
- const score = scoreRestoreLedger(ledger, workboard);
732247
- const mtimeMs = statSync58(filePath).mtimeMs;
732248
- return { ledger: { ...ledger, runId }, workboard, score, mtimeMs };
732249
- }).filter((item) => Boolean(item));
732250
- candidates.sort((a2, b) => b.score - a2.score || b.mtimeMs - a2.mtimeMs);
732251
- const selected = candidates.find((candidate) => candidate.score >= 18);
732252
- return selected ?? null;
732253
- } catch {
732254
- return null;
732255
- }
732256
- }
732257
732204
  function formatActiveTaskAnchor(selected) {
732258
732205
  const ledger = selected.ledger;
732259
732206
  const evidence = ledger.evidence ?? [];
@@ -732285,31 +732232,9 @@ ${evidenceLines.join("\n")}
732285
732232
  ${unresolvedLines.join("\n")}
732286
732233
  ` : "") + (boardCards.length > 0 ? `Active workboard cards:
732287
732234
  ${boardCards.join("\n")}
732288
- ` : "") + `Continuation rule: when the user asks to continue, resume this active task unless the new prompt names a different target.
732235
+ ` : "") + `Continuation rule: this frontier is active only because its exact durable interruption identity was selected.
732289
732236
  </active-task-anchor>`;
732290
732237
  }
732291
- function uniqueSessionIds(ids) {
732292
- const seen = /* @__PURE__ */ new Set();
732293
- const out = [];
732294
- for (const raw of ids) {
732295
- const id2 = String(raw || "").trim();
732296
- if (!id2 || seen.has(id2)) continue;
732297
- seen.add(id2);
732298
- out.push(id2);
732299
- }
732300
- return out;
732301
- }
732302
- function collectRestoreSessionIds(ctx3, activeTask, historySessionId) {
732303
- const entries2 = ctx3?.entries ?? [];
732304
- const usefulEntries = entries2.filter((entry) => !isManualSessionEntry(entry));
732305
- const chronological = usefulEntries.length > 0 ? usefulEntries : entries2;
732306
- return uniqueSessionIds([
732307
- activeTask?.ledger.runId,
732308
- ...[...chronological].reverse().map((entry) => entry.sessionId),
732309
- ...[...entries2].reverse().map((entry) => entry.sessionId),
732310
- historySessionId
732311
- ]);
732312
- }
732313
732238
  function childMapForTodos(todos) {
732314
732239
  const byId = new Set(todos.map((todo) => todo.id));
732315
732240
  const byParent = /* @__PURE__ */ new Map();
@@ -732382,20 +732307,8 @@ ${treeLines.join("\n")}${omitted > 0 ? `
732382
732307
  Continuation contract: this is the live checklist from the restored run. Continue it and update it with todo_write; do not recreate a parallel plan unless the new user request explicitly changes direction.
732383
732308
  </restored-todo-state>`;
732384
732309
  }
732385
- function findRestoredTodoState(sessionIds) {
732386
- for (const sessionId of sessionIds) {
732387
- const todos = readTodos(sessionId).filter((todo) => todo && todo.id && todo.content);
732388
- if (todos.length === 0) continue;
732389
- return {
732390
- sessionId,
732391
- todos,
732392
- block: buildRestoredTodoBlock(sessionId, todos)
732393
- };
732394
- }
732395
- return null;
732396
- }
732397
- function buildRestoreHistoryAnchor(repoRoot) {
732398
- const [latest] = listSessions(repoRoot);
732310
+ function buildRestoreHistoryAnchor(repoRoot, exactSessionId) {
732311
+ const latest = exactSessionId ? listSessions(repoRoot).find((entry) => entry.id === exactSessionId) : listSessions(repoRoot)[0];
732399
732312
  if (!latest?.id) return null;
732400
732313
  const lines = loadSessionHistory(repoRoot, latest.id) ?? [];
732401
732314
  const recordedLineCount = lines.filter((line) => String(line ?? "").trim()).length;
@@ -732411,6 +732324,76 @@ Recall contract: do not replay or ingest the transcript wholesale. Use the struc
732411
732324
  </restored-interface-history>`;
732412
732325
  return { sessionId: latest.id, path: path16, lineCount: recordedLineCount, block };
732413
732326
  }
732327
+ function buildExactContinuationSnapshot(repoRoot, pendingTask) {
732328
+ const identity3 = pendingTask.interruption;
732329
+ if (!identity3) return null;
732330
+ if (pendingTask.projectRoot && resolve77(pendingTask.projectRoot) !== resolve77(repoRoot)) {
732331
+ return null;
732332
+ }
732333
+ const ledgerPath2 = join162(
732334
+ repoRoot,
732335
+ OMNIUS_DIR2,
732336
+ "completion-ledgers",
732337
+ `${identity3.runId}.json`
732338
+ );
732339
+ const rawLedger = readJsonOrNull(ledgerPath2);
732340
+ const ledger = rawLedger && (!rawLedger.runId || rawLedger.runId === identity3.runId) && (!rawLedger.taskEpoch || rawLedger.taskEpoch === identity3.taskEpoch) ? { ...rawLedger, runId: identity3.runId } : null;
732341
+ const rawWorkboard = readRestoreWorkboard(repoRoot, identity3.runId);
732342
+ const workboard = rawWorkboard && (!rawWorkboard.runId || rawWorkboard.runId === identity3.runId) && (!rawWorkboard.taskEpoch || rawWorkboard.taskEpoch === identity3.taskEpoch) ? rawWorkboard : null;
732343
+ const activeTaskAnchor = ledger ? formatActiveTaskAnchor({ ledger, workboard }) : null;
732344
+ const exactTodos = readTodos(identity3.sessionId).filter(
732345
+ (todo) => todo && todo.id && todo.content
732346
+ );
732347
+ const restoredTodos = exactTodos.length > 0 ? {
732348
+ sessionId: identity3.sessionId,
732349
+ todos: exactTodos,
732350
+ block: buildRestoredTodoBlock(identity3.sessionId, exactTodos)
732351
+ } : null;
732352
+ const historyAnchor = buildRestoreHistoryAnchor(repoRoot, identity3.sessionId);
732353
+ const contextEntries = (loadSessionContext(repoRoot)?.entries ?? []).filter((entry) => entry.sessionId === identity3.sessionId).sort((a2, b) => a2.savedAt.localeCompare(b.savedAt)).slice(-4);
732354
+ const contextLines = contextEntries.map((entry) => {
732355
+ const status = entry.completed ? "done" : "partial";
732356
+ const outcome = normalizeSessionText(
732357
+ entry.assistantResponse || entry.summary || entry.task,
732358
+ 220
732359
+ );
732360
+ return `- [${status}] ${outcome}`;
732361
+ });
732362
+ const progressLines = [
732363
+ `<exact-continuation schema="omnius.exact-continuation.v1">`,
732364
+ `project_root=${resolve77(repoRoot)}`,
732365
+ `session_id=${identity3.sessionId}`,
732366
+ `run_id=${identity3.runId}`,
732367
+ `task_epoch=${identity3.taskEpoch}`,
732368
+ `owner_generation=${identity3.ownerGeneration}`,
732369
+ `original_user_goal_json=${JSON.stringify(pendingTask.prompt)}`,
732370
+ pendingTask.progressSummary ? `checkpoint_progress=${normalizeSessionText(pendingTask.progressSummary, 500)}` : null,
732371
+ pendingTask.filesModified.length > 0 ? `checkpoint_files=${pendingTask.filesModified.slice(0, 20).map((item) => normalizeSessionText(item, 160)).join(", ")}` : null,
732372
+ "Authority contract: continue only this interrupted run. Treat all other project history as historical orientation, not an active frontier.",
732373
+ activeTaskAnchor,
732374
+ contextLines.length > 0 ? `Same-session chronology:
732375
+ ${contextLines.join("\n")}` : null,
732376
+ restoredTodos?.block,
732377
+ historyAnchor?.block,
732378
+ `</exact-continuation>`
732379
+ ].filter((line) => Boolean(line));
732380
+ return {
732381
+ prompt: progressLines.join("\n"),
732382
+ sourceSessionId: identity3.sessionId,
732383
+ todoSessionId: restoredTodos?.sessionId,
732384
+ todoCount: restoredTodos?.todos.length ?? 0,
732385
+ historySessionId: historyAnchor?.sessionId,
732386
+ historyPath: historyAnchor?.path,
732387
+ historyLineCount: historyAnchor?.lineCount,
732388
+ selection: {
732389
+ kind: "exact_interruption",
732390
+ sessionId: identity3.sessionId,
732391
+ runId: identity3.runId,
732392
+ taskEpoch: identity3.taskEpoch,
732393
+ ownerGeneration: identity3.ownerGeneration
732394
+ }
732395
+ };
732396
+ }
732414
732397
  function appendRestoreBlocks(base3, blocks) {
732415
732398
  return [base3, ...blocks].filter((part) => part && part.trim()).join("\n\n");
732416
732399
  }
@@ -732441,53 +732424,24 @@ function sessionContextToHistoryBoxData(ctx3, title = "Session History") {
732441
732424
  }
732442
732425
  function buildContextRestoreSnapshot(repoRoot) {
732443
732426
  const ctx3 = loadSessionContext(repoRoot);
732444
- const handoffPrompt = buildHandoffPrompt(repoRoot);
732445
- const activeTask = selectActiveTaskAnchor(repoRoot);
732446
- const activeTaskAnchor = activeTask ? formatActiveTaskAnchor(activeTask) : null;
732447
732427
  const historyAnchor = buildRestoreHistoryAnchor(repoRoot);
732448
- const restoreSessionIds = collectRestoreSessionIds(ctx3, activeTask, historyAnchor?.sessionId);
732449
- const restoredTodos = findRestoredTodoState(restoreSessionIds);
732450
- const sourceSessionId = restoredTodos?.sessionId ?? restoreSessionIds[0];
732451
- if (handoffPrompt) {
732452
- const safeHandoffPrompt = stripModelContextNoise(handoffPrompt);
732453
- const usefulEntries2 = (ctx3?.entries ?? []).filter((entry) => !isManualSessionEntry(entry));
732454
- const baseCtx = ctx3 && ctx3.entries.length > 0 ? `
732455
-
732456
- <session-recap>
732457
- Recent tasks: ${(usefulEntries2.length > 0 ? usefulEntries2 : ctx3.entries).slice(-3).map(
732458
- (e2) => `[${e2.completed ? "done" : "partial"}] ${normalizeSessionText(e2.summary || e2.task, 80)}`
732459
- ).join(", ")}
732460
- </session-recap>` : "";
732461
- const prompt2 = appendRestoreBlocks(
732462
- safeHandoffPrompt + (activeTaskAnchor ? `
732463
-
732464
- ${activeTaskAnchor}` : "") + baseCtx,
732465
- [restoredTodos?.block, historyAnchor?.block]
732466
- );
732467
- return {
732468
- prompt: prompt2,
732469
- sourceSessionId,
732470
- todoSessionId: restoredTodos?.sessionId,
732471
- todoCount: restoredTodos?.todos.length ?? 0,
732472
- historySessionId: historyAnchor?.sessionId,
732473
- historyPath: historyAnchor?.path,
732474
- historyLineCount: historyAnchor?.lineCount
732475
- };
732476
- }
732428
+ const usefulEntries = (ctx3?.entries ?? []).filter(
732429
+ (entry) => !isManualSessionEntry(entry)
732430
+ );
732431
+ const chronological = usefulEntries.length > 0 ? usefulEntries : ctx3?.entries ?? [];
732432
+ const sourceSessionId = chronological.at(-1)?.sessionId ?? historyAnchor?.sessionId;
732477
732433
  if (!ctx3 || ctx3.entries.length === 0) {
732478
- const prompt2 = appendRestoreBlocks(activeTaskAnchor ?? "", [restoredTodos?.block, historyAnchor?.block]);
732434
+ const prompt2 = appendRestoreBlocks("", [historyAnchor?.block]);
732479
732435
  if (!prompt2.trim()) return null;
732480
732436
  return {
732481
732437
  prompt: prompt2,
732482
732438
  sourceSessionId,
732483
- todoSessionId: restoredTodos?.sessionId,
732484
- todoCount: restoredTodos?.todos.length ?? 0,
732439
+ todoCount: 0,
732485
732440
  historySessionId: historyAnchor?.sessionId,
732486
732441
  historyPath: historyAnchor?.path,
732487
732442
  historyLineCount: historyAnchor?.lineCount
732488
732443
  };
732489
732444
  }
732490
- const usefulEntries = ctx3.entries.filter((entry) => !isManualSessionEntry(entry));
732491
732445
  const recent = (usefulEntries.length > 0 ? usefulEntries : ctx3.entries).slice(-20);
732492
732446
  const chronology = recent.map((e2) => {
732493
732447
  const status = e2.completed ? "done" : "partial";
@@ -732511,9 +732465,7 @@ Provenance: ${last2.provenance} (file_read to expand)` : "";
732511
732465
  KG summary: .omnius/context/kg-summary/latest.md (file_read to expand; legacy pointer: .omnius/context/kg-summary-latest.md)`;
732512
732466
  const prompt = appendRestoreBlocks(
732513
732467
  `<session-recap>
732514
- ` + (activeTaskAnchor ? `${activeTaskAnchor}
732515
-
732516
- ` : "") + `Project chronology (older to newer):
732468
+ Project chronology (older to newer):
732517
732469
  Scope: full retained rolling window (${recent.length} entr${recent.length === 1 ? "y" : "ies"}).
732518
732470
  ${chronology.join("\n")}
732519
732471
  ` + (latestCompleted ? `
@@ -732521,16 +732473,14 @@ ${latestCompleted}
732521
732473
  ` : "\n") + `
732522
732474
  Recent structured task state (older to newer; transcript omitted from model context):
732523
732475
  ${recentTaskState}
732524
- ` + (activeTaskAnchor ? `For continuation prompts, resume the active task anchor above; use chronology only to avoid repeating completed work.${prov}${kg}
732525
- ` : `Continue from the latest exchange and do not repeat completed work.${prov}${kg}
732526
- `) + `</session-recap>`,
732527
- [restoredTodos?.block, historyAnchor?.block]
732476
+ This chronology is historical orientation only. It does not select an active task, todo tree, or run. Use an exact interruption checkpoint or an explicit lifecycle command for continuation authority.${prov}${kg}
732477
+ </session-recap>`,
732478
+ [historyAnchor?.block]
732528
732479
  );
732529
732480
  return {
732530
732481
  prompt,
732531
732482
  sourceSessionId,
732532
- todoSessionId: restoredTodos?.sessionId,
732533
- todoCount: restoredTodos?.todos.length ?? 0,
732483
+ todoCount: 0,
732534
732484
  historySessionId: historyAnchor?.sessionId,
732535
732485
  historyPath: historyAnchor?.path,
732536
732486
  historyLineCount: historyAnchor?.lineCount
@@ -732982,7 +732932,7 @@ function deleteUsageRecord(kind, value2, repoRoot) {
732982
732932
  remove(join162(repoRoot, OMNIUS_DIR2, USAGE_HISTORY_FILE));
732983
732933
  }
732984
732934
  }
732985
- var OMNIUS_DIR2, LEGACY_DIRS, SUBDIRS, gitignoreWatchers, gitignoreRetryTimers, CONTEXT_FILES, PENDING_TASK_FILE, HANDOFF_FILE, CONTEXT_SAVE_FILE, CONTEXT_LEDGER_FILE, COMPACTION_AUDIT_FILE, MAX_CONTEXT_ENTRIES, MAX_SESSION_DIARY_ENTRIES, MAX_SESSION_DIARY_DETAILED_ENTRIES, MAX_CONTEXT_LEDGER_LINES, MAX_CONTEXT_LEDGER_BYTES, SAME_TASK_REPLACE_WINDOW_MS, LOCK_TIMEOUT_MS, LOCK_RETRY_MS, LOCK_RETRY_MAX, MODEL_CONTEXT_NOISE_LINE_RE, DEFAULT_RESTORED_SESSION_HISTORY_MAX_LINES, DEICTIC_CONTINUATION_TERMS, SESSIONS_DIR, SESSIONS_INDEX, TUI_STATE_SUFFIX, AUTHORED_SESSION_LINE, VISUAL_CHROME_LINE, SESSION_TITLE_STATUS_LINE, SKIP_DIRS2, HOME_SKIP_DIRS, USAGE_HISTORY_FILE, MAX_HISTORY_RECORDS;
732935
+ var OMNIUS_DIR2, LEGACY_DIRS, SUBDIRS, gitignoreWatchers, gitignoreRetryTimers, CONTEXT_FILES, PENDING_TASK_FILE, HANDOFF_FILE, CONTEXT_SAVE_FILE, CONTEXT_LEDGER_FILE, COMPACTION_AUDIT_FILE, MAX_CONTEXT_ENTRIES, MAX_SESSION_DIARY_ENTRIES, MAX_SESSION_DIARY_DETAILED_ENTRIES, MAX_CONTEXT_LEDGER_LINES, MAX_CONTEXT_LEDGER_BYTES, SAME_TASK_REPLACE_WINDOW_MS, LOCK_TIMEOUT_MS, LOCK_RETRY_MS, LOCK_RETRY_MAX, MODEL_CONTEXT_NOISE_LINE_RE, DEFAULT_RESTORED_SESSION_HISTORY_MAX_LINES, SESSIONS_DIR, SESSIONS_INDEX, TUI_STATE_SUFFIX, AUTHORED_SESSION_LINE, VISUAL_CHROME_LINE, SESSION_TITLE_STATUS_LINE, SKIP_DIRS2, HOME_SKIP_DIRS, USAGE_HISTORY_FILE, MAX_HISTORY_RECORDS;
732986
732936
  var init_omnius_directory = __esm({
732987
732937
  "packages/cli/src/tui/omnius-directory.ts"() {
732988
732938
  init_dist5();
@@ -733019,34 +732969,6 @@ var init_omnius_directory = __esm({
733019
732969
  LOCK_RETRY_MAX = 100;
733020
732970
  MODEL_CONTEXT_NOISE_LINE_RE = /^\s*(?:🔊|E Task timeout reached|E Incomplete:|⚠ Task incomplete|Tokens:\s*[\d,]+|▹\s*continue\b|·\s*(?:Starting fresh|Context (?:auto-)?restored|Nexus P2P network connected|REST API:|Voice feedback enabled|Memory maintenance:|Memory:|reclaimed\s+(?:\d|space\b)|physical footprint:)|.*\bSHELL TRANSCRIPT\b.*).*$/i;
733021
732971
  DEFAULT_RESTORED_SESSION_HISTORY_MAX_LINES = 600;
733022
- DEICTIC_CONTINUATION_TERMS = /* @__PURE__ */ new Set([
733023
- "again",
733024
- "and",
733025
- "back",
733026
- "carry",
733027
- "complete",
733028
- "continue",
733029
- "finish",
733030
- "from",
733031
- "last",
733032
- "latest",
733033
- "left",
733034
- "my",
733035
- "off",
733036
- "please",
733037
- "previous",
733038
- "prior",
733039
- "proceed",
733040
- "request",
733041
- "resume",
733042
- "task",
733043
- "that",
733044
- "the",
733045
- "this",
733046
- "to",
733047
- "try",
733048
- "work"
733049
- ]);
733050
732972
  SESSIONS_DIR = "sessions";
733051
732973
  SESSIONS_INDEX = "sessions-index.json";
733052
732974
  TUI_STATE_SUFFIX = ".tui-state.json";
@@ -767195,7 +767117,7 @@ sleep 1
767195
767117
  }
767196
767118
  const paused = ctx3.pauseTask?.() ?? false;
767197
767119
  if (interruptionAccepted(paused)) {
767198
- ctx3.savePendingTaskState?.();
767120
+ ctx3.savePendingTaskState?.({ reason: "pause", resumeOnStartup: false });
767199
767121
  renderInfo(interruptionMessage(paused, "Task paused."));
767200
767122
  } else {
767201
767123
  renderWarning(interruptionMessage(paused, "Could not pause the task."));
@@ -777335,7 +777257,7 @@ async function handleUpdate(subcommand, ctx3) {
777335
777257
  `Updated Omnius v${currentVersion} → v${terminal.installed_version ?? info.latestVersion}; daemon v${terminal.daemon_version ?? info.latestVersion}${terminal.tray_was_running ? ", indicator restarted" : ""}.`
777336
777258
  );
777337
777259
  ctx3.contextSave?.();
777338
- ctx3.savePendingTaskState?.();
777260
+ ctx3.savePendingTaskState?.({ reason: "update", resumeOnStartup: true });
777339
777261
  if (ctx3.hasActiveTask?.()) ctx3.abortActiveTask?.({ preserveResume: true });
777340
777262
  ctx3.killEphemeral?.({ preserveInfrastructure: true });
777341
777263
  process.exit(120);
@@ -777924,7 +777846,7 @@ async function handleUpdate(subcommand, ctx3) {
777924
777846
  !daemonUpgrade.ok ? `Daemon still at ${daemonUpgrade.observedVersion ?? "unknown"} — restarting client anyway` : daemonUpgrade.action === "restarted" ? `Daemon upgraded to ${expectedDaemonVersion}` : `Daemon verified at ${expectedDaemonVersion}`
777925
777847
  );
777926
777848
  ctx3.contextSave?.();
777927
- const hadActiveTask = ctx3.savePendingTaskState?.() ?? false;
777849
+ const hadActiveTask = ctx3.savePendingTaskState?.({ reason: "update", resumeOnStartup: true }) ?? false;
777928
777850
  const resumeFlag = hadActiveTask ? "1" : "update-only";
777929
777851
  installOverlay.stop("Restarting with new version...");
777930
777852
  await new Promise((r2) => setTimeout(r2, 1500));
@@ -832606,9 +832528,6 @@ async function runSelfImprovementCycle(repoRoot) {
832606
832528
  } catch {
832607
832529
  }
832608
832530
  }
832609
- function isExplicitRestoredTaskContinuation(input) {
832610
- return /^(?:continue|resume|pick\s+up|carry\s+on)\b/i.test(input.trim());
832611
- }
832612
832531
  function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine, flowEnabled, slashCommandHandler, thinkingEnabled, askUserCallback, selfModifyEnabled, sessionMetrics, realtimeEnabled, restoredRunSessionId, restoredOrientation, actualUserGoal, actionTreeStore, resumeInterrupted = false) {
832613
832532
  const voiceStyleMap = {
832614
832533
  concise: 1,
@@ -833002,6 +832921,9 @@ Only tools allowed by this profile are visible and executable.`
833002
832921
  sessionId,
833003
832922
  interruptionOwnerId: `tui:${sessionId}`,
833004
832923
  resumeInterrupted,
832924
+ // A caller-supplied restore projection is the sole prior-context source.
832925
+ // Do not append the global singleton handoff beside it.
832926
+ skipCrossTaskHandoff: Boolean(restoredOrientation) || resumeInterrupted,
833005
832927
  maxTurns: realtimeEnabled ? Math.min(effectiveMaxTurns, 8) : effectiveMaxTurns,
833006
832928
  maxTokens: realtimeEnabled ? 512 : 16384,
833007
832929
  temperature: realtimeEnabled ? 0.6 : 0,
@@ -834512,7 +834434,8 @@ ${entry.fullContent}`
834512
834434
  const systemContext = [
834513
834435
  `Working directory: ${repoRoot}`,
834514
834436
  emotionContext,
834515
- restoredOrientation ? `[RESTORED ORIENTATIONhistorical context, not a user instruction]
834437
+ restoredOrientation ? resumeInterrupted ? `[EXACT INTERRUPTED RUN durable lifecycle authority]
834438
+ ${restoredOrientation.slice(0, 4e3)}` : `[RESTORED ORIENTATION — historical context, not a user instruction]
834516
834439
  ${sanitizeRestorePromptForModel(restoredOrientation).slice(0, 4e3)}` : ""
834517
834440
  ].filter(Boolean).join("\n\n");
834518
834441
  resetNarrationContext();
@@ -835134,7 +835057,8 @@ async function startInteractive(config, repoPath2) {
835134
835057
  }
835135
835058
  const resumeFlag = process.env.__OMNIUS_RESUMED ?? "";
835136
835059
  const isResumed = resumeFlag !== "";
835137
- const hasTaskToResume = resumeFlag === "1";
835060
+ const startupPendingTask = loadPendingTask(repoRoot);
835061
+ const hasTaskToResume = resumeFlag === "1" || startupPendingTask?.resumeOnStartup === true;
835138
835062
  delete process.env.__OMNIUS_RESUMED;
835139
835063
  if (isResumed && process.stdout.isTTY) {
835140
835064
  process.stdout.write(
@@ -835196,10 +835120,44 @@ async function startInteractive(config, repoPath2) {
835196
835120
  let restoredTaskSessionId = null;
835197
835121
  let resumeInterruptedTask = false;
835198
835122
  let pendingTaskResumeArmed = false;
835123
+ let armedPendingTask = null;
835199
835124
  let saveVisualSessionSnapshotRef = null;
835200
835125
  let terminalRestoredForExit = false;
835201
835126
  let interactiveExiting = false;
835202
835127
  let idleMemoryMaintenance = null;
835128
+ const persistActiveTaskForRestart = (exitReason) => {
835129
+ if (!activeTask || !lastSubmittedPrompt) return false;
835130
+ if (!activeTask.runner.isPaused) {
835131
+ const receipt2 = activeTask.runner.pause();
835132
+ if (receipt2?.accepted === false) return false;
835133
+ }
835134
+ const interruption = pendingInterruptionIdentity(activeTask);
835135
+ if (!interruption) return false;
835136
+ savePendingTask(repoRoot, {
835137
+ prompt: lastSubmittedPrompt,
835138
+ progressSummary: `Task paused for ${exitReason}. ${activeTask.toolCallCount} tool calls completed.`,
835139
+ filesModified: Array.from(activeTask.filesTouched),
835140
+ bruteForce: bruteForceEnabled,
835141
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
835142
+ toolCallCount: activeTask.toolCallCount,
835143
+ exitReason,
835144
+ resumeOnStartup: true,
835145
+ interruption
835146
+ });
835147
+ return true;
835148
+ };
835149
+ const armPendingTaskResume = (pendingTask) => {
835150
+ restoredTaskSessionId = pendingTask.interruption?.sessionId ?? null;
835151
+ resumeInterruptedTask = Boolean(pendingTask.interruption);
835152
+ pendingTaskResumeArmed = true;
835153
+ armedPendingTask = pendingTask;
835154
+ const exactSnapshot = buildExactContinuationSnapshot(repoRoot, pendingTask);
835155
+ restoredSessionContext = exactSnapshot?.prompt ?? [
835156
+ `Original user goal: ${pendingTask.prompt}`,
835157
+ pendingTask.progressSummary,
835158
+ pendingTask.filesModified.length > 0 ? `Modified: ${pendingTask.filesModified.slice(0, 20).join(", ")}` : ""
835159
+ ].filter(Boolean).join("\n");
835160
+ };
835203
835161
  const restoreTerminalForExit = () => {
835204
835162
  if (!process.stdout.isTTY || terminalRestoredForExit) return;
835205
835163
  terminalRestoredForExit = true;
@@ -835231,18 +835189,7 @@ async function startInteractive(config, repoPath2) {
835231
835189
  }
835232
835190
  if (activeTask) {
835233
835191
  if (interruption === "pause") {
835234
- const receipt2 = activeTask.runner.pause();
835235
- if (receipt2?.accepted !== false && lastSubmittedPrompt) {
835236
- savePendingTask(repoRoot, {
835237
- prompt: lastSubmittedPrompt,
835238
- progressSummary: `Task paused during process shutdown. ${activeTask.toolCallCount} tool calls completed.`,
835239
- filesModified: Array.from(activeTask.filesTouched),
835240
- bruteForce: bruteForceEnabled,
835241
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
835242
- toolCallCount: activeTask.toolCallCount,
835243
- interruption: pendingInterruptionIdentity(activeTask)
835244
- });
835245
- }
835192
+ persistActiveTaskForRestart("shutdown");
835246
835193
  } else {
835247
835194
  activeTask.runner.abort();
835248
835195
  discardPendingTask(repoRoot);
@@ -837089,9 +837036,9 @@ This is an independent background session started from /background.`
837089
837036
  if (options2.injectNextTask !== false) {
837090
837037
  restoredSessionContext = snapshot.prompt;
837091
837038
  }
837092
- const restoredId = snapshot.todoSessionId || snapshot.sourceSessionId || null;
837093
- restoredTaskSessionId = restoredId;
837094
- if (restoredId) {
837039
+ const restoredId = options2.bindTaskIdentity && snapshot.selection ? snapshot.selection.sessionId : null;
837040
+ if (restoredId && snapshot.selection?.kind === "exact_interruption") {
837041
+ restoredTaskSessionId = restoredId;
837095
837042
  try {
837096
837043
  process.env["OMNIUS_SESSION_ID"] = restoredId;
837097
837044
  } catch {
@@ -838195,8 +838142,14 @@ The user pasted a clipboard image saved at ${relPath}. Use the OCR, vision analy
838195
838142
  } catch {
838196
838143
  }
838197
838144
  },
838198
- savePendingTaskState() {
838145
+ savePendingTaskState(options2) {
838199
838146
  if (lastSubmittedPrompt && activeTask) {
838147
+ if (!activeTask.runner.isPaused) {
838148
+ const receipt2 = activeTask.runner.pause();
838149
+ if (receipt2?.accepted === false) return false;
838150
+ }
838151
+ const interruption = pendingInterruptionIdentity(activeTask);
838152
+ if (!interruption) return false;
838200
838153
  const activeToolCallCount = activeTask.toolCallCount;
838201
838154
  const activeFilesTouched = Array.from(activeTask.filesTouched);
838202
838155
  savePendingTask(repoRoot, {
@@ -838206,7 +838159,9 @@ The user pasted a clipboard image saved at ${relPath}. Use the OCR, vision analy
838206
838159
  bruteForce: bruteForceEnabled,
838207
838160
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
838208
838161
  toolCallCount: activeToolCallCount,
838209
- interruption: pendingInterruptionIdentity(activeTask)
838162
+ exitReason: options2?.reason ?? "pause",
838163
+ resumeOnStartup: options2?.resumeOnStartup === true,
838164
+ interruption
838210
838165
  });
838211
838166
  return true;
838212
838167
  }
@@ -839881,19 +839836,11 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
839881
839836
  resumeTask() {
839882
839837
  const pendingTask = loadPendingTask(repoRoot);
839883
839838
  if (!pendingTask) return false;
839884
- restoredTaskSessionId = pendingTask.interruption?.sessionId ?? null;
839885
- resumeInterruptedTask = Boolean(pendingTask.interruption);
839886
- pendingTaskResumeArmed = true;
839839
+ armPendingTaskResume(pendingTask);
839887
839840
  setTimeout(() => {
839888
- const resumeContext = [
839889
- `Continuing previous task: ${pendingTask.prompt}`,
839890
- pendingTask.progressSummary ? `Progress: ${pendingTask.progressSummary}` : "",
839891
- pendingTask.filesModified.length > 0 ? `Modified: ${pendingTask.filesModified.slice(0, 5).join(", ")}` : "",
839892
- "Continue where you left off."
839893
- ].filter(Boolean).join("\n");
839894
839841
  const shortPrompt = pendingTask.prompt.slice(0, 40) + (pendingTask.prompt.length > 40 ? "..." : "");
839895
839842
  writeContent(() => renderInfo(`Picking up: ${shortPrompt}`));
839896
- rl.emit("line", resumeContext);
839843
+ rl.emit("line", pendingTask.prompt);
839897
839844
  }, 100);
839898
839845
  return true;
839899
839846
  },
@@ -840151,7 +840098,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
840151
840098
  }
840152
840099
  statusBar.ensureMonitorTimer();
840153
840100
  showPrompt();
840154
- if (!isResumed) {
840101
+ if (!isResumed && !hasTaskToResume) {
840155
840102
  const savedCtx = loadSessionContext(repoRoot);
840156
840103
  if (savedCtx && savedCtx.entries.length > 0) {
840157
840104
  const lastEntry = savedCtx.entries[savedCtx.entries.length - 1];
@@ -840277,30 +840224,13 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
840277
840224
  }
840278
840225
  startReminderDispatcher();
840279
840226
  if (hasTaskToResume) {
840280
- const pendingTask = loadPendingTask(repoRoot);
840227
+ const pendingTask = startupPendingTask ?? loadPendingTask(repoRoot);
840281
840228
  if (pendingTask) {
840282
- restoredTaskSessionId = pendingTask.interruption?.sessionId ?? null;
840283
- resumeInterruptedTask = Boolean(pendingTask.interruption);
840284
- pendingTaskResumeArmed = true;
840229
+ armPendingTaskResume(pendingTask);
840285
840230
  setTimeout(() => {
840286
- const restoreSnapshot = buildContextRestoreSnapshot(repoRoot);
840287
- if (restoreSnapshot) {
840288
- applyRestoreSnapshot(restoreSnapshot, {
840289
- replayVisual: false,
840290
- injectNextTask: false
840291
- });
840292
- }
840293
- const sessionCtx = restoreSnapshot?.prompt ?? buildContextRestorePrompt(repoRoot);
840294
- const resumeContext = [
840295
- sessionCtx || "",
840296
- `Continuing task after update: ${pendingTask.prompt}`,
840297
- pendingTask.progressSummary ? `Progress: ${pendingTask.progressSummary}` : "",
840298
- pendingTask.filesModified.length > 0 ? `Modified: ${pendingTask.filesModified.slice(0, 5).join(", ")}` : "",
840299
- "Continue where you left off."
840300
- ].filter(Boolean).join("\n");
840301
840231
  const shortPrompt = pendingTask.prompt.slice(0, 40) + (pendingTask.prompt.length > 40 ? "..." : "");
840302
840232
  writeContent(() => renderInfo(`Picking up: ${shortPrompt}`));
840303
- rl.emit("line", resumeContext);
840233
+ rl.emit("line", pendingTask.prompt);
840304
840234
  }, 100);
840305
840235
  }
840306
840236
  }
@@ -840492,10 +840422,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
840492
840422
  if (quitMatch === "quit" || quitMatch === "exit" || quitMatch === "q") {
840493
840423
  interactiveExiting = true;
840494
840424
  saveVisualSessionSnapshot();
840495
- if (activeTask) {
840496
- activeTask.runner.abort();
840497
- discardPendingTask(repoRoot);
840498
- }
840425
+ if (activeTask) persistActiveTaskForRestart("quit");
840499
840426
  idleMemoryMaintenance?.stop();
840500
840427
  taskManager.stopAll();
840501
840428
  if (blessEngine?.isActive) blessEngine.stop();
@@ -840513,10 +840440,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
840513
840440
  if (cmdResult === "exit") {
840514
840441
  interactiveExiting = true;
840515
840442
  saveVisualSessionSnapshot();
840516
- if (activeTask) {
840517
- activeTask.runner.abort();
840518
- discardPendingTask(repoRoot);
840519
- }
840443
+ if (activeTask) persistActiveTaskForRestart("quit");
840520
840444
  idleMemoryMaintenance?.stop();
840521
840445
  taskManager.stopAll();
840522
840446
  if (telegramBridge) await telegramBridge.stopAndWait();
@@ -841078,6 +841002,10 @@ ${formatTaskCompletionMeta(previousMetaForIntake)}`
841078
841002
  );
841079
841003
  }
841080
841004
  if (promptRecallCancelled) return;
841005
+ const pendingTaskForAdmission = pendingTaskResumeArmed ? armedPendingTask : null;
841006
+ if (pendingTaskForAdmission) {
841007
+ fullInput = pendingTaskForAdmission.prompt;
841008
+ }
841081
841009
  lastSubmittedPrompt = fullInput;
841082
841010
  const taskPreview = fullInput.length > 100 ? fullInput.slice(0, 100) + "..." : fullInput;
841083
841011
  emotionEngine.setCurrentTask(taskPreview);
@@ -841094,18 +841022,19 @@ ${formatTaskCompletionMeta(previousMetaForIntake)}`
841094
841022
  } catch {
841095
841023
  }
841096
841024
  let taskInput = fullInput;
841097
- const reuseRestoredTaskSession = Boolean(restoredTaskSessionId) && (resumeInterruptedTask || isExplicitRestoredTaskContinuation(fullInput));
841025
+ const reuseRestoredTaskSession = Boolean(restoredTaskSessionId) && resumeInterruptedTask && Boolean(pendingTaskForAdmission?.interruption);
841098
841026
  let taskSessionId = reuseRestoredTaskSession ? restoredTaskSessionId : null;
841099
841027
  const adoptInterruptedGeneration = reuseRestoredTaskSession && resumeInterruptedTask;
841100
841028
  const consumePendingTaskAfterAdmission = pendingTaskResumeArmed;
841101
841029
  let restoredOrientation = null;
841102
841030
  if (restoredSessionContext) {
841103
- restoredOrientation = sanitizeRestorePromptForModel(restoredSessionContext);
841031
+ restoredOrientation = pendingTaskForAdmission ? restoredSessionContext : sanitizeRestorePromptForModel(restoredSessionContext);
841104
841032
  restoredSessionContext = null;
841105
841033
  }
841106
841034
  restoredTaskSessionId = null;
841107
841035
  resumeInterruptedTask = false;
841108
841036
  pendingTaskResumeArmed = false;
841037
+ armedPendingTask = null;
841109
841038
  if (taskIntakePacket) {
841110
841039
  taskInput = `${taskIntakePacket}
841111
841040
 
@@ -841162,14 +841091,23 @@ ${taskInput}`;
841162
841091
  realtimeEnabled,
841163
841092
  taskSessionId,
841164
841093
  restoredOrientation,
841165
- fullInput,
841094
+ pendingTaskForAdmission?.prompt ?? fullInput,
841166
841095
  idleActionTree,
841167
841096
  adoptInterruptedGeneration
841168
841097
  );
841169
841098
  activeTask = task;
841170
841099
  if (consumePendingTaskAfterAdmission) {
841171
841100
  const adopted = task.runner.interruptionState;
841172
- if (!adoptInterruptedGeneration || adopted?.phase === "running" && adopted.sessionId === taskSessionId) {
841101
+ if (pendingTaskForAdmission && !pendingTaskForAdmission.interruption || pendingTaskForAdmission && pendingTaskMatchesRunningInterruption(
841102
+ pendingTaskForAdmission,
841103
+ adopted ? {
841104
+ sessionId: adopted.sessionId,
841105
+ runId: adopted.runId,
841106
+ taskEpoch: adopted.taskEpoch,
841107
+ ownerGeneration: adopted.owner.generation,
841108
+ phase: adopted.phase
841109
+ } : null
841110
+ )) {
841173
841111
  discardPendingTask(repoRoot);
841174
841112
  }
841175
841113
  }
@@ -841227,6 +841165,21 @@ ${taskInput}`;
841227
841165
  scheduleAgentSNREval(lastSubmittedPrompt);
841228
841166
  }
841229
841167
  if (activeTask) {
841168
+ if (pendingTaskForAdmission?.interruption) {
841169
+ const state5 = activeTask.runner.interruptionState;
841170
+ if (state5 && state5.phase !== "running" && state5.phase !== "pausing" && state5.phase !== "paused" && state5.phase !== "resuming" && pendingTaskMatchesInterruptionIdentity(
841171
+ pendingTaskForAdmission,
841172
+ {
841173
+ sessionId: state5.sessionId,
841174
+ runId: state5.runId,
841175
+ taskEpoch: state5.taskEpoch,
841176
+ ownerGeneration: state5.owner.generation,
841177
+ phase: state5.phase
841178
+ }
841179
+ )) {
841180
+ discardPendingTask(repoRoot);
841181
+ }
841182
+ }
841230
841183
  sessionFilesTouched = Array.from(activeTask.filesTouched);
841231
841184
  sessionToolCallCount = activeTask.toolCallCount;
841232
841185
  sessionAvailableTools = Array.from(activeTask.availableTools);
@@ -841506,7 +841459,7 @@ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.cat
841506
841459
  peerMesh = null;
841507
841460
  inferenceRouter = null;
841508
841461
  }
841509
- if (activeTask) activeTask.runner.abort();
841462
+ if (activeTask) persistActiveTaskForRestart("shutdown");
841510
841463
  taskManager.stopAll();
841511
841464
  killAllFullSubAgents();
841512
841465
  idleMemoryMaintenance?.stop();
@@ -841600,6 +841553,8 @@ ${c3.dim("(Use /quit to exit)")}
841600
841553
  bruteForce: bruteForceEnabled,
841601
841554
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
841602
841555
  toolCallCount: activeTask.toolCallCount,
841556
+ exitReason: "pause",
841557
+ resumeOnStartup: false,
841603
841558
  interruption: pendingInterruptionIdentity(activeTask)
841604
841559
  });
841605
841560
  }
@@ -33786,6 +33786,44 @@
33786
33786
  }
33787
33787
  ]
33788
33788
  },
33789
+ {
33790
+ "id": "guide.work-orders-runtime-health-remediation-wo-20-exact-session-continuation-uppercase",
33791
+ "kind": "guide",
33792
+ "title": "WO-20: Exact session continuation across exit and restart",
33793
+ "summary": "On 2026-09-03, the telegramtest TUI displayed the latest pentest task during startup, but the first restored model context combined a completed breach.py handoff with an unrelated, older page.tsx todo tree. The user then entered continue, and Omnius continued the stale tree instead of the task that was active before /quit.",
33794
+ "keywords": [
33795
+ "work",
33796
+ "orders",
33797
+ "runtime",
33798
+ "health",
33799
+ "remediation",
33800
+ "WO",
33801
+ "20",
33802
+ "exact",
33803
+ "session",
33804
+ "continuation",
33805
+ "md"
33806
+ ],
33807
+ "maturity": "internal",
33808
+ "audiences": [
33809
+ "maintainer",
33810
+ "large-context-agent"
33811
+ ],
33812
+ "layer": "documentation",
33813
+ "interfaces": [
33814
+ {
33815
+ "type": "file",
33816
+ "target": "docs/work-orders/runtime-health-remediation/WO-20-exact-session-continuation.md"
33817
+ }
33818
+ ],
33819
+ "references": [
33820
+ {
33821
+ "type": "documentation",
33822
+ "target": "docs/work-orders/runtime-health-remediation/WO-20-exact-session-continuation.md",
33823
+ "relation": "canonical-artifact"
33824
+ }
33825
+ ]
33826
+ },
33789
33827
  {
33790
33828
  "id": "guide.work-orders-telegram-dropbear-context-rca-workorder",
33791
33829
  "kind": "guide",
package/docs/DISCOVERY.md CHANGED
@@ -558,6 +558,7 @@ Daemon equivalents are `GET /v1/discovery/bootstrap`, `GET /v1/discovery?q=<inte
558
558
  | `guide.work-orders-runtime-health-remediation-wo-18-interruption-evidence-uppercase` | WO-18 interruption lifecycle evidence | Scope: /pause, /resume, /stop, safe-boundary ownership, recovery, and stale-effect prevention. This record is independent of the shared remediation tracker and records only verified WO-18 implementation evidence. |
559
559
  | `guide.work-orders-runtime-health-remediation-wo-19-clean-build-evidence-uppercase` | WO-19 Clean Build Evidence | WO-19 is implemented in commit 5f253c05. |
560
560
  | `guide.work-orders-runtime-health-remediation-wo-19-clean-build-reproducibility-uppercase` | WO-19: Clean Build and Optional-Dependency Reproducibility | Status: deterministic acceptance complete Risk: medium Depends on: WO-00 |
561
+ | `guide.work-orders-runtime-health-remediation-wo-20-exact-session-continuation-uppercase` | WO-20: Exact session continuation across exit and restart | On 2026-09-03, the telegramtest TUI displayed the latest pentest task during startup, but the first restored model context combined a completed breach.py handoff with an unrelated, older page.tsx todo tree. The user then entered continue, and Omnius continued the stale tree instead of the task that was active before /quit. |
561
562
  | `guide.work-orders-telegram-dropbear-context-rca-workorder` | Telegram Dropbear Context Engineering RCA Work Order | Observed run: /home/roko/Documents/Projects/Adjacent/telegramtest/.omnius, run id 1782873796963-i5r7mv. |
562
563
  | `guide.work-orders-wo-am-gaps-uppercase` | Associative Memory Gap Work Orders | Generated: 2026-04-13 Source: Deep audit of multimodal associative memory systems Status: READY FOR IMPLEMENTATION |
563
564
  | `guide.work-orders-world-class-memory-compiler-readme-uppercase` | World-Class Memory Compiler Program | Status: active implementation program Owner: Omnius orchestration and memory packages Last updated: 2026-07-13 |
@@ -309,3 +309,5 @@ deterministically repaired P0 or P1 defect.
309
309
  accessibility, and mocked inference end-to-end tests.
310
310
  - [x] WO-18 pause, stop, and resume use one durable interruption lifecycle and
311
311
  pass cancellation, restart, ownership, external-effect, and isolation tests.
312
+ - [x] WO-20 exit and restart preserve one exact typed task continuation. Generic
313
+ context restoration cannot mix or activate unrelated historical artifacts.
@@ -0,0 +1,88 @@
1
+ # WO-20: Exact session continuation across exit and restart
2
+
3
+ ## Incident
4
+
5
+ On 2026-09-03, the `telegram_test` TUI displayed the latest pentest task during
6
+ startup, but the first restored model context combined a completed `breach.py`
7
+ handoff with an unrelated, older `page.tsx` todo tree. The user then entered
8
+ `continue`, and Omnius continued the stale tree instead of the task that was
9
+ active before `/quit`.
10
+
11
+ ## Root causes
12
+
13
+ - `/quit`, `/exit`, readline close, and fallback exit abort an active runner.
14
+ The explicit quit paths also delete `.omnius/history/pending-task.json`.
15
+ - Startup selects handoff, completion ledger, todo state, context chronology,
16
+ and visual history independently. These projections need not identify the
17
+ same session or run.
18
+ - Generic restore mutates the process-wide task session, even though the
19
+ restored transcript is historical orientation only.
20
+ - The host interprets words such as `continue` with a regular expression and
21
+ uses that classification to reuse a restored task identity.
22
+ - A resumed task is submitted as a generated wrapper. The wrapper can replace
23
+ the original user goal in new completion, workboard, and handoff records.
24
+ - Pending-task consumption checks only phase and session. It does not prove the
25
+ recovered run, epoch, and owner generation.
26
+ - Updating an existing session-context entry changes its timestamp in place,
27
+ but does not move it into chronological order. Tail readers can therefore
28
+ report an older task as latest.
29
+
30
+ ## Required invariants
31
+
32
+ - [x] An active task receives a typed, atomic continuation checkpoint before a
33
+ resumable exit.
34
+ - [x] `/quit`, `/exit`, readline close, and graceful process termination pause
35
+ and preserve the active run. Explicit `/stop`, Ctrl+C, and double-Esc remain
36
+ terminal and delete the pending checkpoint.
37
+ - [x] A checkpoint identifies the project, original goal, session, run, task
38
+ epoch, owner generation, lifecycle phase, exit reason, and capture time.
39
+ - [x] Manual startup automatically adopts a checkpoint explicitly marked for
40
+ restart, without requiring an updater environment variable.
41
+ - [x] Exact continuation context is scoped to the checkpoint session and run.
42
+ A global handoff, ledger, todo tree, or transcript cannot replace it.
43
+ - [x] Generic context restore is orientation only. It does not bind task/todo
44
+ identity or mutate `OMNIUS_SESSION_ID`.
45
+ - [x] Continuation authority comes from typed lifecycle state or an explicit
46
+ command. It never comes from semantic keyword or regular-expression
47
+ classification of user prose.
48
+ - [x] The resumed runner receives the exact original user goal. Progress and
49
+ historical orientation remain separate context.
50
+ - [x] A pending checkpoint is consumed only after the runner proves the same
51
+ session, run, epoch, and owner generation in the running phase.
52
+ - [x] Session-context entries remain ordered by their effective save time after
53
+ merge or replacement.
54
+ - [x] Corrupt, cross-project, incomplete, or identity-mismatched checkpoints
55
+ fail closed and remain available for diagnosis unless explicitly stopped.
56
+
57
+ ## Deterministic verification
58
+
59
+ - [x] Pending checkpoint round-trip and schema validation.
60
+ - [x] Exact four-field adoption match and one-field-at-a-time mismatch table.
61
+ - [x] Quit wiring has no abort/discard path for an active resumable run.
62
+ - [x] Restart continuation remains exact in the presence of a newer handoff,
63
+ higher-scoring stale ledger, unrelated todos, and unrelated visual history.
64
+ - [x] Generic restore cannot bind a subsequent fresh task to historical todos.
65
+ - [x] A resumed run persists the original goal, not a synthetic continuation
66
+ wrapper.
67
+ - [x] Session-context merge reorders the updated entry chronologically.
68
+ - [x] Focused CLI typecheck/build and affected tests pass.
69
+
70
+ ## Delivery evidence
71
+
72
+ Implemented in the CLI session directory, TUI lifecycle coordinator, command
73
+ surface, and runner continuation gate. Verification on 2026-09-03:
74
+
75
+ - Focused CLI interruption, restore, session-diary, and command suites: 52
76
+ passed.
77
+ - Focused orchestrator context and interruption suites: 67 passed.
78
+ - Complete orchestrator suite: 2,292 passed, 14 skipped.
79
+ - Complete CLI suite: 2,425 passed. Two unrelated tests timed out while the CLI
80
+ and orchestrator suites ran concurrently; both passed alone, 35 of 35.
81
+ - CLI and orchestrator typechecks passed.
82
+ - CLI and orchestrator builds passed.
83
+ - A two-process, no-inference harness wrote the checkpoint in one process and
84
+ recovered the exact `harness-session/harness-run/epoch-3` selection in a
85
+ second process.
86
+
87
+ No live inference or service restart was required for this host-side
88
+ state-machine repair.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.688",
3
+ "version": "1.0.689",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.688",
9
+ "version": "1.0.689",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.688",
3
+ "version": "1.0.689",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/library.js",