oasis_test 0.1.109 → 0.1.111

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 (2) hide show
  1. package/dist/index.js +342 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4102,12 +4102,15 @@ ${m2.spec.split("\n").map((l) => ` > ${l}`).join("\n")}`);
4102
4102
  // 刻意不写「过去 24 小时发生 N 次」这类统计:任务书是长期提示词,写死的数字会过期变成假话。
4103
4103
  `\u4F60\u8FD9\u4E00\u8F6E\u968F\u65F6\u53EF\u80FD\u88AB\u780D\u2014\u2014\u8D85\u65F6\u3001\u989D\u5EA6\u6253\u6EE1\u3001\u8282\u70B9\u6389\u7EBF\u90FD\u4F1A\uFF0C\u800C\u4E14**\u662F\u5E38\u4E8B**\u3002\u771F\u88AB\u780D\u65F6\uFF0C\u4E0B\u4E00\u8D9F\u63A5\u624B\u7684\u4EBA\u53EA\u770B\u5F97\u5230\u300C\u4F60\u4E3A\u4EC0\u4E48\u65AD\u300D\uFF0C\u770B\u4E0D\u5230\u300C\u4F60\u5E72\u5230\u54EA\u300D\u2014\u2014\u9664\u975E\u4F60\u7559\u4E86\u65AD\u70B9\u3002`,
4104
4104
  `\u505A\u5B8C\u4E00\u4E2A\u9636\u6BB5\u3001\u6216\u8981\u5F00\u59CB\u4E00\u4EF6\u53EF\u80FD\u5F88\u4E45\u7684\u4E8B\u4E4B\u524D\uFF0C\u987A\u624B\u8BB0\u4E00\u6761\uFF08\u51E0\u79D2\u8FD4\u56DE\uFF0C\u4E0D\u6253\u65AD\u4F60\uFF09\uFF1A`,
4105
+ // ★ 参数必须是 **JSON string array**(CLI 侧 `JSON.parse` 后校验,见 cli.ts parseStringArrayFlag)。
4106
+ // 这里给的是能逐字复制的真例子——写成自然语言一句话会当场抛 `--pending 必须是 JSON string array`。
4105
4107
  `\`\`\``,
4106
4108
  `oasis continuity-checkpoint --trigger stage_completed \\`,
4107
- ` --completed "\u5DF2\u5B8C\u6210\u7684\u6B65\u9AA4,\u9017\u53F7\u5206\u9694\uFF08\u7528\u7A33\u5B9A\u7684\u540D\u5B57\uFF0C\u91CD\u8DD1\u65F6\u540C\u4E00\u6B65\u540C\u4E00\u4E2A\u540D\uFF09" \\`,
4108
- ` --pending "\u8FD8\u5269\u4EC0\u4E48\u6CA1\u505A" \\`,
4109
- ` --recoverability full|partial|none`,
4109
+ ` --completed '["\u8BFB\u9700\u6C42","\u6539 auth \u4E2D\u95F4\u4EF6","\u8DD1\u5355\u6D4B"]' \\`,
4110
+ ` --pending '["\u8865\u96C6\u6210\u6D4B\u8BD5","\u5199\u7ED3\u6848\u62A5\u544A"]' \\`,
4111
+ ` --recoverability partial`,
4110
4112
  `\`\`\``,
4113
+ `\u4E24\u4E2A\u6570\u7EC4\u90FD\u662F JSON\uFF08\u5355\u5F15\u53F7\u5305\u4F4F\u3001\u91CC\u9762\u7528\u53CC\u5F15\u53F7\uFF09\u3002\u6B65\u9AA4\u540D\u7528\u7A33\u5B9A\u7684\u8BF4\u6CD5\u2014\u2014\u91CD\u8DD1\u65F6\u540C\u4E00\u6B65\u7528\u540C\u4E00\u4E2A\u540D\uFF0C\u4E0B\u4E00\u8D9F\u624D\u8BA4\u5F97\u51FA\u54EA\u4E9B\u5DF2\u7ECF\u505A\u5B8C\u3002\`--recoverability\` \u4E09\u9009\u4E00\uFF1A\`full\`=\u63A5\u624B\u7684\u4EBA\u7167\u7740\u5C31\u80FD\u7EED\u4E0A\u3001\`partial\`=\u80FD\u7EED\u4F46\u8981\u91CD\u505A\u4E00\u90E8\u5206\u3001\`none\`=\u7B49\u4E8E\u4ECE\u5934\u518D\u6765\u3002`,
4111
4114
  `**\u53EA\u6709\u4F60\u77E5\u9053\u300C\u8FD8\u5269\u4EC0\u4E48\u300D**\u2014\u2014\u7CFB\u7EDF\u80FD\u81EA\u52A8\u8BB0\u4E0B\u4F60\u8DD1\u8FC7\u54EA\u4E9B\u547D\u4EE4\u3001\u4EA4\u5230\u4E86\u54EA\u4E00\u7248\uFF0C\u4F46\u63A8\u4E0D\u51FA\u4F60\u6253\u7B97\u5E72\u4EC0\u4E48\u3002\u522B\u5199\u7A7A\u8BDD\uFF1B\u6CA1\u4EC0\u4E48\u53EF\u5199\u5C31\u4E0D\u5199\uFF0C\u522B\u4E3A\u4E86\u586B\u800C\u586B\u3002`
4112
4115
  ],
4113
4116
  // 兜底(§10):上次失败了 → 明确告诉它发生了什么 + 怎么破(首选 add-revision 拆出去)
@@ -158906,7 +158909,12 @@ async function cachedDiscovery(key, fn) {
158906
158909
  if (entry && now < entry.expiresAt) {
158907
158910
  return entry.models;
158908
158911
  }
158909
- const models = await fn();
158912
+ let models;
158913
+ try {
158914
+ models = await fn();
158915
+ } catch {
158916
+ return [];
158917
+ }
158910
158918
  modelCache.set(key, { models, expiresAt: now + CACHE_TTL_MS });
158911
158919
  return models;
158912
158920
  }
@@ -158976,12 +158984,77 @@ async function discoverCursorModels(bin) {
158976
158984
  async function discoverOpenCodeModels(bin) {
158977
158985
  throw new Error("discoverOpenCodeModels: TODO");
158978
158986
  }
158987
+ function isPiDiscoveryNoise(line) {
158988
+ const lower = line.toLowerCase();
158989
+ if (lower.includes("no models match pattern")) return true;
158990
+ if (lower.startsWith("warning:") || lower.startsWith("error:") || lower.startsWith("info:")) return true;
158991
+ return line.includes("`") || lower.includes("--help") || lower.includes("usage:") || lower.includes("unknown flag") || lower.includes("unknown command");
158992
+ }
158993
+ function parsePiModels(output) {
158994
+ const models = [];
158995
+ const seen = /* @__PURE__ */ new Set();
158996
+ for (const raw of output.split("\n")) {
158997
+ const line = raw.trim();
158998
+ if (!line || isPiDiscoveryNoise(line)) continue;
158999
+ const fields = line.split(/\s+/).filter(Boolean);
159000
+ if (fields.length === 0) continue;
159001
+ const first = fields[0];
159002
+ if (first.toLowerCase() === "provider") continue;
159003
+ let id;
159004
+ if (/[:/]/.test(first)) {
159005
+ id = first.replace(":", "/");
159006
+ } else if (fields.length >= 2) {
159007
+ id = `${first}/${fields[1]}`;
159008
+ } else {
159009
+ continue;
159010
+ }
159011
+ const slash = id.indexOf("/");
159012
+ if (slash <= 0 || slash === id.length - 1) continue;
159013
+ if (seen.has(id)) continue;
159014
+ seen.add(id);
159015
+ models.push({ id, label: id, provider: id.slice(0, slash) });
159016
+ }
159017
+ return models;
159018
+ }
158979
159019
  async function discoverPiModels(bin) {
158980
- throw new Error("discoverPiModels: TODO");
159020
+ const out = await runCapture(bin ?? "pi", ["--list-models"]);
159021
+ return out === void 0 ? [] : parsePiModels(out);
158981
159022
  }
158982
159023
  async function discoverAntigravityModels(bin) {
158983
159024
  throw new Error("discoverAntigravityModels: TODO");
158984
159025
  }
159026
+ async function runCapture(bin, args, timeoutMs = 15e3) {
159027
+ return await new Promise((resolve10) => {
159028
+ let child;
159029
+ try {
159030
+ child = (0, import_node_child_process8.spawn)(bin, args, { stdio: ["ignore", "pipe", "ignore"], env: { ...process.env } });
159031
+ } catch {
159032
+ resolve10(void 0);
159033
+ return;
159034
+ }
159035
+ let out = "";
159036
+ let settled = false;
159037
+ const done = (v2) => {
159038
+ if (!settled) {
159039
+ settled = true;
159040
+ clearTimeout(timer);
159041
+ resolve10(v2);
159042
+ }
159043
+ };
159044
+ const timer = setTimeout(() => {
159045
+ try {
159046
+ child.kill("SIGKILL");
159047
+ } catch {
159048
+ }
159049
+ done(void 0);
159050
+ }, timeoutMs);
159051
+ child.stdout?.on("data", (d) => {
159052
+ out += d.toString();
159053
+ });
159054
+ child.on("error", () => done(void 0));
159055
+ child.on("close", (code) => done(code === 0 ? out : void 0));
159056
+ });
159057
+ }
158985
159058
  async function discoverACPModels(bin, provider, args = ["acp"]) {
158986
159059
  const child = (() => {
158987
159060
  try {
@@ -166721,12 +166794,11 @@ function systemHandoffFor(run, checkpoint = null, effects = []) {
166721
166794
  summary: `[system_reconstructed] attempt=${run.id} outcome=${outcome} reason=${terminalReason}; ${progress}`.slice(0, 400),
166722
166795
  changedOutputs: structuredClone(run.outputRefs),
166723
166796
  unresolvedIssues: [],
166724
- risks: [
166725
- ...run.errorMessage ? [run.errorMessage.slice(0, 400)] : [],
166726
- ...pendingEffects || failedEffects ? [`external effects: pending=${pendingEffects}, failed=${failedEffects}`] : []
166727
- ],
166797
+ // 与 PG 触发器同口径(2026-08-18):risks / verificationGaps 只装业务语义的东西。会话怎么死的
166798
+ // 已在 summary reason= 段里,不再冒充成「交付物的风险」;「没有断点」也不是「验证缺口」。
166799
+ risks: pendingEffects || failedEffects ? [`external effects: pending=${pendingEffects}, failed=${failedEffects}`] : [],
166728
166800
  nextActions: structuredClone(checkpoint?.pendingSteps ?? []),
166729
- verificationGaps: checkpoint ? [] : ["No valid pre-terminal checkpoint was available."],
166801
+ verificationGaps: [],
166730
166802
  userHandoff: null,
166731
166803
  createdAt: run.endedAt ?? run.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
166732
166804
  };
@@ -167116,6 +167188,13 @@ var init_memory_trace_store = __esm({
167116
167188
  const handoff = this.handoffs.get(attemptId);
167117
167189
  return handoff ? structuredClone(handoff) : null;
167118
167190
  }
167191
+ async getHandoffByOutputVersion(version2) {
167192
+ const hits = [...this.handoffs.values()].filter((h) => h.changedOutputs.some((ref2) => ref2.version === version2));
167193
+ if (hits.length === 0) return null;
167194
+ let best = hits[0];
167195
+ for (const h of hits.slice(1)) if (h.createdAt >= best.createdAt) best = h;
167196
+ return structuredClone(best);
167197
+ }
167119
167198
  async listAttempts(workOrderId) {
167120
167199
  return [...this.runs.values()].filter((run) => run.workOrderId === workOrderId && run.nodeId !== null).sort((a, b2) => (a.nodeId ?? "").localeCompare(b2.nodeId ?? "") || (a.attemptNo ?? 0) - (b2.attemptNo ?? 0)).map((run) => this.decorate(structuredClone(run)));
167121
167200
  }
@@ -171253,6 +171332,79 @@ var init_orphan_sweep = __esm({
171253
171332
  }
171254
171333
  });
171255
171334
 
171335
+ // ../server/src/zero-output-breaker.ts
171336
+ async function scanZeroOutputBreaker(deps) {
171337
+ const tripped = [];
171338
+ const skipped = [];
171339
+ const countEnabled = deps.threshold > 0;
171340
+ const costEnabled = (deps.costThresholdMicros ?? 0) > 0;
171341
+ if (!countEnabled && !costEnabled) return { tripped, skipped };
171342
+ const sinceIso = new Date(deps.now - deps.windowMs).toISOString();
171343
+ const runs = await deps.listRecentZeroOutputRuns(sinceIso);
171344
+ const buckets = /* @__PURE__ */ new Map();
171345
+ for (const r of runs) {
171346
+ if (r.artifactId === null) continue;
171347
+ if (!deps.actions.has(r.action ?? "")) continue;
171348
+ const startedMs = Date.parse(r.startedAt);
171349
+ if (!Number.isFinite(startedMs)) continue;
171350
+ const b2 = buckets.get(r.artifactId);
171351
+ if (!b2) {
171352
+ buckets.set(r.artifactId, {
171353
+ count: 1,
171354
+ costUsdMicros: r.costUsdMicros ?? 0,
171355
+ latestRunId: r.id,
171356
+ latestStartedMs: startedMs
171357
+ });
171358
+ } else {
171359
+ b2.count += 1;
171360
+ b2.costUsdMicros += r.costUsdMicros ?? 0;
171361
+ if (startedMs >= b2.latestStartedMs) {
171362
+ b2.latestStartedMs = startedMs;
171363
+ b2.latestRunId = r.id;
171364
+ }
171365
+ }
171366
+ }
171367
+ for (const [artifactId, b2] of buckets) {
171368
+ const hitCount = countEnabled && b2.count >= deps.threshold;
171369
+ const hitCost = costEnabled && b2.costUsdMicros >= (deps.costThresholdMicros ?? 0);
171370
+ if (!hitCount && !hitCost) continue;
171371
+ if (deps.isHeld(artifactId) || deps.hasOpenEscalation?.(artifactId)) {
171372
+ skipped.push(artifactId);
171373
+ continue;
171374
+ }
171375
+ const closingRemark = await deps.closingRemarkOf(b2.latestRunId).catch(() => null);
171376
+ await deps.trip({
171377
+ artifactId,
171378
+ count: b2.count,
171379
+ costUsdMicros: b2.costUsdMicros,
171380
+ windowMs: deps.windowMs,
171381
+ latestRunId: b2.latestRunId,
171382
+ closingRemark
171383
+ });
171384
+ tripped.push(artifactId);
171385
+ deps.log?.(
171386
+ `[zero-output-breaker] ${artifactId}\uFF1A\u6700\u8FD1 ${Math.round(deps.windowMs / 6e4)} \u5206\u949F\u5185\u7D2F\u8BA1 ${b2.count} \u6B21\u96F6\u4EA7\u51FA\uFF08\u7EA6 $${(b2.costUsdMicros / 1e6).toFixed(1)}\uFF09\u2192 hold + \u6295\u9012\u6536\u4EF6\u7BB1`
171387
+ );
171388
+ }
171389
+ return { tripped, skipped };
171390
+ }
171391
+ function formatZeroOutputNotice(t) {
171392
+ const usd2 = (t.costUsdMicros / 1e6).toFixed(1);
171393
+ const mins = Math.round(t.windowMs / 6e4);
171394
+ const remark = t.closingRemark && t.closingRemark.trim() ? `
171395
+
171396
+ \u6700\u8FD1\u4E00\u6B21\u8FD0\u884C\u7684\u6536\u5C3E\u53D1\u8A00\uFF08\u5F80\u5F80\u76F4\u63A5\u5199\u7740\u5361\u5728\u54EA\uFF09\uFF1A
171397
+ ${t.closingRemark.trim()}` : "";
171398
+ return `\u8FD9\u4E2A\u4EA7\u7269\u5728\u6700\u8FD1 ${mins} \u5206\u949F\u5185\u88AB\u53CD\u590D\u53EB\u9192\u3001\u7D2F\u8BA1\u6709 ${t.count} \u6B21\u8FD0\u884C**\u4EC0\u4E48\u90FD\u6CA1\u4EA4\u51FA\u6765**\uFF08\u8FD9\u4E9B\u7A7A\u8DD1\u7D2F\u8BA1\u82B1\u8D39\u7EA6 $${usd2}\uFF09\uFF0C\u5DF2\u81EA\u52A8\u6682\u505C\u6D3E\u53D1\u4EE5\u6B62\u635F\u3002
171399
+
171400
+ \u8FD9\u4E0D\u662F\u5728\u5224\u67D0\u4E00\u6B21\u8FD0\u884C\u5931\u8D25\u2014\u2014\u662F\u540C\u4E00\u4E2A\u4EA7\u7269\u5728\u8FD9\u6BB5\u65F6\u95F4\u91CC\u53CD\u590D\u96F6\u4EA7\u51FA\u3002\u65E0\u8BBA\u6BCF\u4E00\u6B21\u9000\u51FA\u591A\u4E48\u300C\u6B63\u5E38\u300D\uFF0C\u53CD\u590D\u5230\u8FD9\u4E2A\u6B21\u6570\u901A\u5E38\u610F\u5473\u7740\u5B83\u5361\u5728\u4E00\u4E2A\u53CD\u590D\u91CD\u8BD5\u4E5F\u8FC7\u4E0D\u53BB\u7684\u574E\u4E0A\uFF08\u7F3A\u51ED\u636E\u3001\u4E0A\u6E38\u670D\u52A1\u8FDE\u4E0D\u4E0A\u3001\u6216\u649E\u4E0A\u4E86\u522B\u7684\u6B7B\u5FAA\u73AF\uFF09\u3002\u8BF7\u770B\u4E00\u773C\u6839\u56E0\uFF1B\u5904\u7406\u5B8C\u7528 \`oasis release\` \u6062\u590D\u6D3E\u53D1\u3002` + remark;
171401
+ }
171402
+ var init_zero_output_breaker = __esm({
171403
+ "../server/src/zero-output-breaker.ts"() {
171404
+ "use strict";
171405
+ }
171406
+ });
171407
+
171256
171408
  // ../server/src/session-exit-outcome.ts
171257
171409
  function isHealthySessionExit(info) {
171258
171410
  return info.code === 0 && (info.reason === void 0 || info.reason === "clean");
@@ -184040,7 +184192,7 @@ function createUpstreamHandoffInputResolver(opts) {
184040
184192
  version: input.pinnedWorkId,
184041
184193
  ...upstreamNode?.title ? { label: upstreamNode.title } : {}
184042
184194
  }];
184043
- const deliveredRow = typeof work.sessionRef === "string" && work.sessionRef ? await opts.getHandoff(work.sessionRef).catch(() => null) : null;
184195
+ const deliveredRow = await opts.getHandoffByOutputVersion(input.pinnedWorkId).catch(() => null);
184044
184196
  const systemHandoff = () => renderDeliveredWorkHandoff({
184045
184197
  upstreamNodeId: input.upstreamNodeId,
184046
184198
  upstreamTitle: upstreamNode?.title,
@@ -187722,8 +187874,30 @@ ${ctx.nodeFault}
187722
187874
  ...prov?.connectorCreds && prov.connectorCreds.length > 0 ? { connectorCreds: prov.connectorCreds } : {}
187723
187875
  };
187724
187876
  this.deps.log?.(`[coordinator] ${logMsg}`);
187877
+ const coordinatorRunId = `coordinate:${runtimeSessionId}`;
187878
+ if (this.deps.recordCoordinatorRun) {
187879
+ await this.deps.recordCoordinatorRun({
187880
+ runId: coordinatorRunId,
187881
+ actorId,
187882
+ artifactId,
187883
+ workOrderId: workspace ?? null,
187884
+ runtimeSessionId,
187885
+ bundleFiles: Object.keys(job.bundle.files)
187886
+ }).catch((error2) => {
187887
+ this.deps.log?.(`[coordinator] \u8D26\u672C\u767B\u8BB0\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u4F1A\u8BDD\uFF09\uFF1A${String(error2)}`);
187888
+ });
187889
+ }
187725
187890
  const handle = await this.deps.adapter.spawn(job);
187726
187891
  const exit = await new Promise((resolve10) => handle.onExit(resolve10));
187892
+ if (this.deps.finishCoordinatorRun) {
187893
+ await this.deps.finishCoordinatorRun(coordinatorRunId, {
187894
+ status: exit.code === 0 ? "succeeded" : "failed",
187895
+ exitCode: exit.code,
187896
+ ...exit.reason ? { exitReason: String(exit.reason) } : {}
187897
+ }).catch((error2) => {
187898
+ this.deps.log?.(`[coordinator] \u8D26\u672C\u6536\u5C3E\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u4F1A\u8BDD\uFF09\uFF1A${String(error2)}`);
187899
+ });
187900
+ }
187727
187901
  await this.finalizeStage(token);
187728
187902
  this.registerRetained(artifactId, actorId, runtimeSessionId);
187729
187903
  return { sessionId: runtimeSessionId, managerActorId: actorId, exit };
@@ -188000,6 +188174,7 @@ var init_src9 = __esm({
188000
188174
  init_roles_seed();
188001
188175
  init_liveness();
188002
188176
  init_orphan_sweep();
188177
+ init_zero_output_breaker();
188003
188178
  init_dispatch_exit_reconcile();
188004
188179
  init_session_exit_outcome();
188005
188180
  init_api_activity();
@@ -190889,6 +191064,9 @@ var init_postgres_trace = __esm({
190889
191064
  await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS part text`);
190890
191065
  await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS user_handoff jsonb`);
190891
191066
  await pool.query(`ALTER TABLE "${s2}".node_handoffs DROP CONSTRAINT IF EXISTS node_handoffs_summary_check`);
191067
+ await pool.query(
191068
+ `CREATE INDEX IF NOT EXISTS node_handoffs_changed_outputs_idx ON "${s2}".node_handoffs USING GIN (changed_outputs)`
191069
+ );
190892
191070
  await pool.query(`
190893
191071
  UPDATE "${s2}".node_handoffs h
190894
191072
  SET job_key=COALESCE(NULLIF(h.job_key, ''), r.job_key, 'attempt:' || h.attempt_id), part=r.part
@@ -190998,15 +191176,26 @@ var init_postgres_trace = __esm({
190998
191176
  END,
190999
191177
  400
191000
191178
  );
191179
+ -- 2026-08-18\uFF1Arisks / verification_gaps \u53EA\u88C5 **\u4E1A\u52A1\u8BED\u4E49** \u7684\u4E1C\u897F\u3002
191180
+ --
191181
+ -- \u75C5\u6839\uFF1A\u8FD9\u91CC\u539F\u672C\u628A NEW.error_message \u585E\u8FDB risks\u3001\u628A\u300C\u6CA1\u6709\u53EF\u7528\u65AD\u70B9\u300D\u585E\u8FDB verification_gaps\u3002
191182
+ -- \u4E24\u8005\u90FD\u4E0D\u662F\u5B83\u4EEC\u5B57\u9762\u7684\u610F\u601D\u2014\u2014\u7EBF\u4E0A\u771F\u5B9E\u503C\u662F
191183
+ -- risks = ["[success] You've hit your session limit - resets 4:40pm (UTC)"]
191184
+ -- verification_gaps = ["No valid pre-terminal checkpoint was available."]
191185
+ -- \u524D\u8005\u662F\u300C\u8FD9\u6B21\u4F1A\u8BDD\u600E\u4E48\u6B7B\u7684\u300D\uFF0C\u540E\u8005\u662F\u300C\u7CFB\u7EDF\u81EA\u5DF1\u6CA1\u6599\u300D\uFF0C\u90FD\u4E0D\u662F\u300C\u4EA4\u4ED8\u7269\u6709\u4EC0\u4E48\u98CE\u9669 / \u54EA\u91CC\u6CA1\u6838\u9A8C\u300D\u3002
191186
+ -- \u5371\u5BB3\u6709\u4E8C\uFF1A(1) \u4EA4\u4ED8\u6210\u529F\u4E4B\u540E\u4F1A\u8BDD\u624D\u8D85\u65F6\u7684\u60C5\u5F62\uFF0C\u8FD9\u4E9B\u4F1A\u88AB\u586B\u8FDB\u7A7A\u5B57\u6BB5\u5E76 **\u771F\u7684\u88AB\u4E0B\u6E38\u8BFB\u5230**\uFF1B
191187
+ -- (2) \u7EDF\u8BA1\u586B\u5145\u7387\u65F6\u5168\u662F\u566A\u97F3\uFF0C\u770B\u8D77\u6765 80% \u6709\u503C\uFF0C\u5176\u5B9E\u6CA1\u4E00\u6761\u662F\u6267\u884C\u4EBA\u5199\u7684\u3002
191188
+ --
191189
+ -- \u4E24\u6761\u4FE1\u606F\u90FD\u4E0D\u4E22\uFF1A\u7EC8\u6001\u539F\u56E0\u5DF2\u5728 summary \u7684 reason= \u6BB5\u91CC\uFF08terminal_reason \u5C31\u53D6\u81EA
191190
+ -- error_message\uFF09\uFF0C\u300C\u6CA1\u6709\u65AD\u70B9\u300D\u4E5F\u5DF2\u5728 summary \u7684 no valid pre-terminal checkpoint \u91CC\u3002
191191
+ -- \u8FD9\u91CC\u53EA\u662F\u4E0D\u518D\u628A\u5B83\u4EEC\u5192\u5145\u6210\u4E1A\u52A1\u5B57\u6BB5\u3002
191192
+ --
191193
+ -- \u5916\u90E8\u6548\u5E94\u7559\u5728 risks\uFF1A\u534A\u6210\u54C1\u526F\u4F5C\u7528\uFF08\u5DF2\u53D1\u51FA\u7684\u8BF7\u6C42 / \u5EFA\u4E86\u4E00\u534A\u7684\u8D44\u6E90\uFF09\u786E\u5B9E\u662F\u4E0B\u6E38\u8BE5\u77E5\u9053\u7684\u98CE\u9669\u3002
191001
191194
  reconstructed_risks :=
191002
- CASE WHEN NEW.error_message IS NULL THEN '[]'::jsonb
191003
- ELSE jsonb_build_array(left(NEW.error_message, 400)) END ||
191004
191195
  CASE WHEN COALESCE(pending_effects, 0) = 0 AND COALESCE(failed_effects, 0) = 0 THEN '[]'::jsonb
191005
191196
  ELSE jsonb_build_array('external effects: pending=' || COALESCE(pending_effects, 0) ||
191006
191197
  ', failed=' || COALESCE(failed_effects, 0)) END;
191007
- reconstructed_gaps := CASE WHEN latest_checkpoint_id IS NULL
191008
- THEN jsonb_build_array('No valid pre-terminal checkpoint was available.')
191009
- ELSE '[]'::jsonb END;
191198
+ reconstructed_gaps := '[]'::jsonb;
191010
191199
  INSERT INTO "${s2}".node_handoffs AS current_handoff
191011
191200
  (handoff_id, attempt_id, job_key, part, produced_by, outcome, summary, changed_outputs,
191012
191201
  unresolved_issues, risks, next_actions, verification_gaps, user_handoff, created_at)
@@ -191954,6 +192143,15 @@ var init_postgres_trace = __esm({
191954
192143
  const r = await this.pool.query(`SELECT * FROM ${this.s}.node_handoffs WHERE attempt_id=$1`, [attemptId]);
191955
192144
  return r.rows[0] ? rowToHandoff(r.rows[0]) : null;
191956
192145
  }
192146
+ async getHandoffByOutputVersion(version2) {
192147
+ const r = await this.pool.query(
192148
+ `SELECT * FROM ${this.s}.node_handoffs
192149
+ WHERE changed_outputs @> $1::jsonb
192150
+ ORDER BY created_at DESC LIMIT 1`,
192151
+ [JSON.stringify([{ version: version2 }])]
192152
+ );
192153
+ return r.rows[0] ? rowToHandoff(r.rows[0]) : null;
192154
+ }
191957
192155
  async listAttempts(workOrderId) {
191958
192156
  const r = await this.pool.query(
191959
192157
  `SELECT r.*, (SELECT count(*) FROM ${this.s}.agent_tool_calls tc WHERE tc.run_id=r.id)::int AS tool_call_count
@@ -197142,7 +197340,8 @@ async function startServe(opts) {
197142
197340
  if (!store) throw new Error(`engine store unavailable for ${companyId}`);
197143
197341
  return store.transaction((tx) => tx.loadWorkorder(workOrderId));
197144
197342
  },
197145
- getHandoff: (attemptId) => traceStore.getHandoff(attemptId)
197343
+ getHandoff: (attemptId) => traceStore.getHandoff(attemptId),
197344
+ getHandoffByOutputVersion: (version2) => traceStore.getHandoffByOutputVersion(version2)
197146
197345
  }),
197147
197346
  executionContinuityInstructions: continuityMode === "off" ? renderBusinessHandoffProtocol() : renderContinuityProtocol(),
197148
197347
  // ★ 恢复包注入(2026-08-17):从「只有 resume 实验组才给」改成「只要账本开着就给」——
@@ -197704,6 +197903,7 @@ ${st.detail}
197704
197903
  let coordTimer;
197705
197904
  let livenessTimer;
197706
197905
  let orphanSweepTimer;
197906
+ let zeroOutputBreakerTimer;
197707
197907
  if (opts.dispatch) {
197708
197908
  const coordinator = new CoordinatorWorker({
197709
197909
  kernel,
@@ -197762,6 +197962,31 @@ ${st.detail}
197762
197962
  const view = await executionContinuity.service.coordinatorView(workOrderId, defaultCompanyId);
197763
197963
  return view ? renderCoordinatorView(view) : null;
197764
197964
  },
197965
+ // D2(2026-08-18):协调者会话进执行账本。inputRefs 记下 bundle 里实际发出的文件——
197966
+ // 这是唯一能证明「COORDINATOR_VIEW.md 到底有没有随会话发出去」的痕迹。两处都 fail-open。
197967
+ recordCoordinatorRun: async (input) => {
197968
+ await traceStore.createRun({
197969
+ id: input.runId,
197970
+ actorId: input.actorId,
197971
+ runtimeKind: "claude-code",
197972
+ triggerKind: "intervention",
197973
+ artifactId: input.artifactId,
197974
+ nodeId: input.artifactId,
197975
+ workOrderId: input.workOrderId,
197976
+ runtimeSessionId: input.runtimeSessionId,
197977
+ jobKey: `coordinate::${input.artifactId}`,
197978
+ action: "coordinate",
197979
+ inputRefs: input.bundleFiles.map((name) => ({ kind: "bundle", id: name }))
197980
+ });
197981
+ },
197982
+ finishCoordinatorRun: async (runId, outcome) => {
197983
+ await traceStore.updateRun(runId, {
197984
+ status: outcome.status,
197985
+ exitCode: outcome.exitCode,
197986
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
197987
+ ...outcome.exitReason ? { exitReason: outcome.exitReason } : {}
197988
+ });
197989
+ },
197765
197990
  // 阻塞诊断(proposal D1/D3):派发器运行时信号(queued/fused/backoff/hasInFlight)注入诊断,
197766
197991
  // 让协调者把"资源 / 运行时"病因和"图病"分开——资源排队的节点不会被误唤去砍流程。
197767
197992
  runtimeSignals: (id) => {
@@ -197868,6 +198093,80 @@ ${st.detail}
197868
198093
  }).catch((err) => console.error(`[orphan-sweep] tick \u5931\u8D25: ${String(err)}`));
197869
198094
  }, orphanIntervalMs);
197870
198095
  }
198096
+ const zbFlag = (process.env["OASIS_ZERO_OUTPUT_BREAKER"] ?? "").trim().toLowerCase();
198097
+ const zbDisabledByEnv = ["0", "false", "off"].includes(zbFlag);
198098
+ const envInt = (name) => {
198099
+ const raw = process.env[name];
198100
+ if (raw === void 0 || raw.trim() === "") return void 0;
198101
+ const n = Number(raw.trim());
198102
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
198103
+ };
198104
+ const zbEnabled = opts.zeroOutputBreaker?.enabled ?? !zbDisabledByEnv;
198105
+ const zbIntervalMs = opts.zeroOutputBreaker?.intervalMs ?? 3e4;
198106
+ const zbWindowMs = opts.zeroOutputBreaker?.windowMs ?? (envInt("OASIS_ZERO_OUTPUT_BREAKER_WINDOW_MIN") ?? 30) * 6e4;
198107
+ const zbThreshold = opts.zeroOutputBreaker?.threshold ?? envInt("OASIS_ZERO_OUTPUT_BREAKER_N") ?? 5;
198108
+ const zbCostThresholdMicros = opts.zeroOutputBreaker?.costThresholdMicros ?? (envInt("OASIS_ZERO_OUTPUT_BREAKER_COST_USD") ?? 0) * 1e6;
198109
+ if (zbEnabled && zbIntervalMs > 0 && (zbThreshold > 0 || zbCostThresholdMicros > 0)) {
198110
+ const zbActions = /* @__PURE__ */ new Set(["produce", "review"]);
198111
+ const listRecentZeroOutputRuns = async (sinceIso) => {
198112
+ const page = await traceStore.listRuns({ status: "no-output", from: sinceIso, limit: 1e3 });
198113
+ if (page.nextCursor) console.warn("[zero-output-breaker] no-output run \u8D85 1000 \u6761\u672A\u7FFB\u9875\uFF0C\u672C\u62CD\u53EA\u626B\u9996\u9875");
198114
+ return page.items.map((r) => ({
198115
+ id: r.id,
198116
+ artifactId: r.artifactId,
198117
+ action: r.action,
198118
+ startedAt: r.startedAt,
198119
+ costUsdMicros: r.usage?.costUsdMicros ?? null
198120
+ }));
198121
+ };
198122
+ const latestClosingRemark = async (runId) => {
198123
+ try {
198124
+ let afterSeq = 0;
198125
+ let last = null;
198126
+ let scanned = 0;
198127
+ for (; ; ) {
198128
+ const rows = await traceStore.listEvents(runId, { afterSeq, limit: 500 });
198129
+ if (rows.length === 0) break;
198130
+ for (const e of rows) {
198131
+ if (e.stream !== "model") continue;
198132
+ const payload = e.payload && typeof e.payload === "object" && !Array.isArray(e.payload) ? e.payload : {};
198133
+ const raw = payload["raw"] && typeof payload["raw"] === "object" && !Array.isArray(payload["raw"]) ? payload["raw"] : {};
198134
+ const kind = payload["kind"] ?? raw["kind"];
198135
+ if (kind === "thought") continue;
198136
+ const text = typeof raw["text"] === "string" ? raw["text"] : typeof e.message === "string" ? e.message : null;
198137
+ if (text && text.trim()) last = text;
198138
+ }
198139
+ afterSeq = rows[rows.length - 1].seq;
198140
+ scanned += rows.length;
198141
+ if (rows.length < 500 || scanned >= 5e3) break;
198142
+ }
198143
+ return last ? last.trim().slice(0, 500) : null;
198144
+ } catch {
198145
+ return null;
198146
+ }
198147
+ };
198148
+ zeroOutputBreakerTimer = setInterval(() => {
198149
+ void scanZeroOutputBreaker({
198150
+ now: Date.now(),
198151
+ windowMs: zbWindowMs,
198152
+ threshold: zbThreshold,
198153
+ costThresholdMicros: zbCostThresholdMicros,
198154
+ actions: zbActions,
198155
+ listRecentZeroOutputRuns,
198156
+ isHeld: (artifactId) => isHeld(kernel.model, artifactId),
198157
+ hasOpenEscalation: (artifactId) => unresolvedEscalationsOf(kernel.model, artifactId).length > 0,
198158
+ closingRemarkOf: latestClosingRemark,
198159
+ // 复用 SLA 同一止损出口:escalate(进收件箱)+ hold(source:"sla",挡派发不锁内容)+ 去重。
198160
+ trip: (t) => escalateHold(t.artifactId, formatZeroOutputNotice(t), "\u96F6\u4EA7\u51FA\u4FDD\u9669\u4E1D"),
198161
+ log: (m2) => console.log(m2)
198162
+ }).catch((err) => console.error(`[zero-output-breaker] tick \u5931\u8D25: ${String(err)}`));
198163
+ }, zbIntervalMs);
198164
+ console.log(
198165
+ `[serve] \u96F6\u4EA7\u51FA\u4FDD\u9669\u4E1D\uFF1A\u7A97\u53E3 ${Math.round(zbWindowMs / 6e4)}min\u3001N=${zbThreshold}${zbCostThresholdMicros > 0 ? `\u3001\u82B1\u8D39\u9608\u503C $${(zbCostThresholdMicros / 1e6).toFixed(0)}` : ""}\u3001\u5468\u671F ${Math.round(zbIntervalMs / 1e3)}s`
198166
+ );
198167
+ } else {
198168
+ console.log("[serve] \u96F6\u4EA7\u51FA\u4FDD\u9669\u4E1D\uFF1A\u5DF2\u5173\u95ED");
198169
+ }
197871
198170
  }
197872
198171
  const automationScheduler = new AutomationScheduler({
197873
198172
  store: automationStore,
@@ -197900,6 +198199,7 @@ ${st.detail}
197900
198199
  if (coordTimer) clearInterval(coordTimer);
197901
198200
  if (livenessTimer) clearInterval(livenessTimer);
197902
198201
  if (orphanSweepTimer) clearInterval(orphanSweepTimer);
198202
+ if (zeroOutputBreakerTimer) clearInterval(zeroOutputBreakerTimer);
197903
198203
  clearInterval(automationTimer);
197904
198204
  if (boardWatchTimer) clearInterval(boardWatchTimer);
197905
198205
  clearInterval(serverHeartbeatTimer);
@@ -197991,13 +198291,14 @@ var KNOWN2 = [
197991
198291
  { adapter: "antigravity", binary: "agy" }
197992
198292
  ];
197993
198293
  var NATIVE_APPEND_RUNTIMES = /* @__PURE__ */ new Set(["hermes", "gemini", "kimi", "kiro", "reasonix"]);
197994
- function onPath(binary) {
198294
+ function resolveOnPath(binary) {
197995
198295
  try {
197996
198296
  const cmd = process.platform === "win32" ? "where" : "which";
197997
- (0, import_node_child_process15.execFileSync)(cmd, [binary], { stdio: "ignore" });
197998
- return true;
198297
+ const out = (0, import_node_child_process15.execFileSync)(cmd, [binary], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
198298
+ const first = out.split("\n")[0]?.trim();
198299
+ return first || binary;
197999
198300
  } catch {
198000
- return false;
198301
+ return void 0;
198001
198302
  }
198002
198303
  }
198003
198304
  function versionOf(binary) {
@@ -198012,11 +198313,11 @@ function versionOf(binary) {
198012
198313
  }
198013
198314
  }
198014
198315
  function detectRuntimes() {
198015
- return KNOWN2.filter((e) => onPath(e.binary)).map((e) => {
198016
- const version2 = versionOf(e.binary);
198316
+ return KNOWN2.map((e) => ({ ...e, resolved: resolveOnPath(e.binary) })).filter((e) => e.resolved !== void 0).map((e) => {
198317
+ const version2 = versionOf(e.resolved);
198017
198318
  return {
198018
198319
  kind: e.adapter,
198019
- binary: e.binary,
198320
+ binary: e.resolved,
198020
198321
  ...version2 ? { version: version2 } : {},
198021
198322
  ...NATIVE_APPEND_RUNTIMES.has(e.adapter) ? { supportsAppendInput: true } : {}
198022
198323
  };
@@ -199453,13 +199754,13 @@ var COMMAND_DECLS = {
199453
199754
  },
199454
199755
  "continuity-checkpoint": {
199455
199756
  group: "\u56FE\u673A\u5236\uFF08\xA76/\xA77\uFF09",
199456
- usage: "oasis continuity-checkpoint --pending <json-string-array> --recoverability full|partial|none [--trigger plan_completed|stage_completed] [--refs <json-array>] [--completed <json-string-array>]",
199757
+ usage: "oasis continuity-checkpoint --pending <list> --recoverability full|partial|none [--trigger plan_completed|stage_completed] [--refs <json-array>] [--completed <list>]",
199457
199758
  description: "\u4E3A\u5F53\u524D\u6D3E\u53D1 attempt \u8FFD\u52A0\u53EF\u6062\u590D\u68C0\u67E5\u70B9\uFF08\u81EA\u52A8\u4FDD\u7559\u6700\u65B0\u4E94\u4E2A\uFF09\u3002",
199458
199759
  flags: [
199459
- { name: "pending", desc: "\u5F85\u5B8C\u6210\u6B65\u9AA4 JSON string array", required: true },
199760
+ { name: "pending", desc: `\u5F85\u5B8C\u6210\u6B65\u9AA4\u3002JSON \u6570\u7EC4 '["a","b"]' \u6216\u5206\u53F7\u5206\u9694 "a; b" \u90FD\u6536`, required: true },
199460
199761
  { name: "recoverability", desc: "\u6062\u590D\u7A0B\u5EA6\uFF08full | partial | none\uFF09", required: true },
199461
199762
  { name: "refs", desc: "\u5DF2\u6301\u4E45\u5316\u4EA7\u51FA\u5F15\u7528 JSON array" },
199462
- { name: "completed", desc: "\u5DF2\u5B8C\u6210\u7A33\u5B9A\u6B65\u9AA4 id JSON string array" },
199763
+ { name: "completed", desc: "\u5DF2\u5B8C\u6210\u7684\u7A33\u5B9A\u6B65\u9AA4 id\u3002JSON \u6570\u7EC4\u6216\u5206\u53F7\u5206\u9694\u90FD\u6536" },
199463
199764
  { name: "trigger", desc: "\u7ED3\u6784\u5316\u8FB9\u754C\uFF08plan_completed | stage_completed\uFF1B\u7701\u7565\u4E3A\u666E\u901A agent checkpoint\uFF09" }
199464
199765
  ],
199465
199766
  examples: [`oasis continuity-checkpoint --pending '["run tests"]' --recoverability full --refs '[{"kind":"file","id":"src/x.ts"}]'`]
@@ -200572,6 +200873,19 @@ function semicolonList(raw, field) {
200572
200873
  const items = raw.split(";").map((x2) => x2.trim()).filter(Boolean);
200573
200874
  return items.length > 0 ? { [field]: items } : {};
200574
200875
  }
200876
+ function parseStringArrayFlag(value, name) {
200877
+ const trimmed = value.trim();
200878
+ if (trimmed.startsWith("[")) {
200879
+ const parsed = JSON.parse(trimmed);
200880
+ if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
200881
+ throw new Error(`--${name} \u5FC5\u987B\u662F JSON string array`);
200882
+ }
200883
+ return parsed;
200884
+ }
200885
+ const items = trimmed.split(";").map((x2) => x2.trim()).filter(Boolean);
200886
+ if (items.length === 0) throw new Error(`--${name} \u4E0D\u80FD\u4E3A\u7A7A\uFF08JSON string array\uFF0C\u6216\u5206\u53F7\u5206\u9694\uFF09`);
200887
+ return items;
200888
+ }
200575
200889
  async function readStdin() {
200576
200890
  if (process.stdin.isTTY) return "";
200577
200891
  const chunks = [];
@@ -201132,13 +201446,6 @@ async function runCli(argv, println = console.log, progressln = console.error) {
201132
201446
  }
201133
201447
  return parsed;
201134
201448
  };
201135
- const parseStringArrayFlag = (value, name) => {
201136
- const parsed = JSON.parse(value);
201137
- if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
201138
- throw new Error(`--${name} \u5FC5\u987B\u662F JSON string array`);
201139
- }
201140
- return parsed;
201141
- };
201142
201449
  const parseArrayFlag = (value, name) => {
201143
201450
  const parsed = JSON.parse(value);
201144
201451
  if (!Array.isArray(parsed)) throw new Error(`--${name} \u5FC5\u987B\u662F JSON array`);
@@ -203242,7 +203549,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
203242
203549
  }
203243
203550
 
203244
203551
  // src/index.ts
203245
- var PKG_VERSION = true ? "0.1.109" : "dev";
203552
+ var PKG_VERSION = true ? "0.1.111" : "dev";
203246
203553
  var OASIS_DIR = path28.join(os11.homedir(), ".oasis");
203247
203554
  var CONFIG_FILE = path28.join(OASIS_DIR, "node-config.json");
203248
203555
  var PID_FILE = path28.join(OASIS_DIR, "node.pid");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"