oasis_test 0.1.60 → 0.1.62

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 +757 -418
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1912,9 +1912,12 @@ var init_kernel = __esm({
1912
1912
  };
1913
1913
  FrozenError = class extends KernelError {
1914
1914
  name = "FrozenError";
1915
- retryable = true;
1916
- constructor(artifactId, scope) {
1917
- super(`artifact is frozen (scope=${scope}, retry after unfreeze): ${artifactId}`);
1915
+ retryable;
1916
+ constructor(artifactId, scope, opts) {
1917
+ super(
1918
+ opts?.reviewPending ? `artifact under review\uFF08\u6536\u5C3E\u8BC4\u5BA1\u8FDB\u884C\u4E2D\uFF0C\u7B49 reviewer/CI \u6295\u7968\u2014\u2014\u522B\u91CD\u8BD5\uFF1B\u6709\u672A\u7B54\u590D\u7684\u56DE\u538B\u5148\u5904\u7F6E\uFF09: ${artifactId}` : `artifact is frozen (scope=${scope}, retry after unfreeze): ${artifactId}`
1919
+ );
1920
+ this.retryable = !opts?.reviewPending;
1918
1921
  }
1919
1922
  };
1920
1923
  Kernel = class _Kernel {
@@ -2305,15 +2308,15 @@ var init_kernel = __esm({
2305
2308
  if (artifact.currentRev === null) {
2306
2309
  throw new KernelError(`cannot conclude an empty artifact: ${args.artifactId}`);
2307
2310
  }
2308
- if (concludeMilestones(this.model, args.artifactId).some((m2) => m2.head === artifact.currentRev)) {
2309
- return;
2310
- }
2311
2311
  const pending = queueOf(this.model, args.artifactId);
2312
2312
  if (pending.length > 0) {
2313
2313
  throw new KernelError(
2314
2314
  `queue not quiescent (${pending.length} pending): \u4E0D\u80FD\u5BF9\u9A6C\u4E0A\u8981\u53D8\u7684 head \u5BA3\u5E03\u5B8C\u6210\u2014\u2014\u5148\u843D\u5B8C/\u62D2\u6389/\u96C6\u6210\uFF08\xA75.7\uFF09`
2315
2315
  );
2316
2316
  }
2317
+ if (concludeMilestones(this.model, args.artifactId).some((m2) => m2.head === artifact.currentRev)) {
2318
+ return { kind: "noop-concluded", head: artifact.currentRev };
2319
+ }
2317
2320
  const openGaps = unresolvedGapsOf(this.model, args.artifactId);
2318
2321
  if (openGaps.length > 0) {
2319
2322
  throw new KernelError(
@@ -2395,7 +2398,11 @@ var init_kernel = __esm({
2395
2398
  ...gate ? { gate } : {}
2396
2399
  };
2397
2400
  await this.commit(args.artifactId, args.actor, "conclude", args.artifactId, payload);
2398
- if (!gate) await this.fireTriggers(args.artifactId);
2401
+ if (!gate) {
2402
+ await this.fireTriggers(args.artifactId);
2403
+ return { kind: "milestone", head: artifact.currentRev };
2404
+ }
2405
+ return { kind: "gate-pending" };
2399
2406
  }
2400
2407
  /**
2401
2408
  * 收尾 gate 投票(§5.3/§5.7)。票落在被审的 head 快照上;
@@ -3354,7 +3361,7 @@ ${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192
3354
3361
  bounceHeadMovers(artifactId, opts) {
3355
3362
  const scope = this.frozen.get(artifactId);
3356
3363
  if (scope) throw new FrozenError(artifactId, scope);
3357
- if (!opts?.allowPendingGate && pendingConcludeAttempt(this.model, artifactId)) throw new FrozenError(artifactId, "merge");
3364
+ if (!opts?.allowPendingGate && pendingConcludeAttempt(this.model, artifactId)) throw new FrozenError(artifactId, "merge", { reviewPending: true });
3358
3365
  }
3359
3366
  /** propose(emit)类:只有 write 档(§11.7 干预)才拦——merge 档下照常入队。 */
3360
3367
  bounceWrites(artifactId) {
@@ -3860,14 +3867,29 @@ async function assembleContext(args) {
3860
3867
  })
3861
3868
  ];
3862
3869
  })() : canvasMode ? [
3863
- `\u4F60\u8D1F\u8D23 **design \u4EA7\u7269 \`${artifactId}\`**\uFF0C\u8FD0\u884C\u5728**\u901A\u7528\u8FD0\u884C\u65F6**\u4E0A\u2014\u2014\u7528 \`oasis design run\` \u9A71\u52A8**\u534F\u4F5C\u753B\u677F\u5F15\u64CE**\u4EA7\u51FA\u5E76\u4EA4\u4ED8\u754C\u9762\u7A3F\uFF1A`,
3870
+ `\u4F60\u8D1F\u8D23 **design \u4EA7\u7269 \`${artifactId}\`**\uFF0C\u8FD0\u884C\u5728**\u901A\u7528\u8FD0\u884C\u65F6**\u4E0A\u2014\u2014\u4F60\u662F**\u7532\u65B9\u8BBE\u8BA1\u8D1F\u8D23\u4EBA**\uFF0C\u591A\u8F6E\u5BF9\u8BDD\u9A71\u52A8**\u534F\u4F5C\u753B\u677F\u5F15\u64CE**\u6253\u78E8\u754C\u9762\u7A3F\uFF0C\u6EE1\u610F\u4E86\u624D\u4EA4\u4ED8\u3002`,
3871
+ ``,
3872
+ `**\u7B2C 0 \u6B65\uFF08\u6BCF\u6B21\u88AB\u6D3E\u53D1\u90FD\u5148\u505A\uFF0C\u518D\u51B3\u5B9A\u5F00\u4E0D\u5F00\u65B0\u8F6E\uFF09\u2014\u2014\u5148\u53D6\u8D27\uFF1A**`,
3873
+ ``,
3874
+ ` oasis design status ${artifactId}`,
3864
3875
  ``,
3865
- ` oasis design run ${artifactId} --reason "<\u4E00\u53E5\u8BDD\u8BF4\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"`,
3876
+ `- \u677F\u4E0A**\u5DF2\u6709\u8DD1\u5B8C\u7684\u4EA4\u4ED8\u7269**\uFF08\u4E0A\u4E00\u4E2A\u4F1A\u8BDD\u59D4\u6258\u7684\u5F15\u64CE\u8DD1\u5B8C\u4E86\uFF0C\u8D27\u6CA1\u4EBA\u6536\uFF09\u2192 \`--pull\` \u62C9\u5230\u672C\u5730**\u76F4\u63A5\u8FDB\u300C\u8BFB\u4EA7\u7269\u300D**\uFF0C\u522B\u91CD\u8DD1\u5F15\u64CE\u767D\u70E7 20-40 \u5206\u949F\uFF1B`,
3877
+ `- **\u5F15\u64CE\u8FD8\u5728\u8DD1** \u2192 \u522B\u518D\u8D77\u65B0\u8F6E\uFF1B\u8FC7\u51E0\u5206\u949F\u518D status\uFF0C\u6216\u672C\u8F6E\u6536\u5DE5\uFF08\u5F15\u64CE\u5728\u670D\u52A1\u7AEF\u81EA\u5DF1\u8DD1\u5B8C\uFF0C\u4E0B\u6B21\u6D3E\u53D1\u6765\u53D6\uFF09\uFF1B`,
3878
+ `- \u4EC0\u4E48\u90FD\u6CA1\u6709 \u2192 \u8D77\u9996\u8F6E\u3002`,
3866
3879
  ``,
3867
- `- \u5B83\u4F1A\uFF1A\u8D77/\u590D\u7528\u672C\u4EA7\u7269\u7684**\u753B\u677F** \u2192 \u8BBE\u8BA1\u5F15\u64CE\u5728\u771F\u5B9E\u753B\u5E03\u4E0A\u4EA7**\u81EA\u5305\u542B HTML** \u5165\u53E3\u7A3F + \`acceptance.md\` \u2192 \u5BFC\u51FA\u5FEB\u7167 \u2192 **\u81EA\u52A8 propose \u4EA4\u7A3F**\u3002\u8BBE\u8BA1\u9700\u6C42\u5B83\u4ECE \`DESIGN_BRIEF.md\` + \`inputs/\` \u81EA\u52A8\u8BFB\u53D6\uFF1B`,
3868
- `- \u4F60**\u4E0D\u7528\u624B\u5199 HTML\u3001\u4E5F\u4E0D\u7528\u624B\u52A8 propose**\u2014\u2014\u8FD9\u6761\u547D\u4EE4\u4E00\u6B65\u5230\u4F4D\uFF1B\u8DD1\u5B8C\u770B\u5B83\u8F93\u51FA\u7684 board \u94FE\u63A5\u4E0E index.html \u786E\u8BA4\u4EA7\u51FA\uFF1B`,
3869
- `- \u547D\u4EE4\u5931\u8D25\uFF08\u5F15\u64CE\u62A5\u9519/\u65E0\u4EA7\u51FA\uFF09\u2192 \u770B\u8F93\u51FA\u5B9A\u4F4D\uFF1B\u7F3A**\u8FD8\u4E0D\u5B58\u5728\u7684\u4E0A\u6E38\u524D\u7F6E**\u505A\u4E0D\u4E0B\u53BB \u2192 \`oasis gap ${artifactId} --desc "<\u7F3A\u4EC0\u4E48>"\` \u62A5\u7F3A\u53E3\u540E\u6536\u5DE5\uFF1B`,
3870
- `- \u5FE0\u5B9E \`DESIGN_BRIEF.md\` \u4E0E \`inputs/\` \u91CC\u7684\u4E0A\u6E38\uFF1B\u4E0D\u5F52\u4F60\u5B9A\u7684\u53D6\u820D\u522B\u786C\u7F16\uFF0C\u8BA9\u5F15\u64CE\u5728 acceptance.md \u7684\u300C\u5F00\u653E\u95EE\u9898\u300D\u91CC\u7559\u5F85\u4EBA\u5DE5\u786E\u8BA4\u3002`
3880
+ `**\u8D77\u4E00\u8F6E\uFF08\u4E0D\u963B\u585E\uFF0C\u79D2\u56DE\uFF09\uFF1A**`,
3881
+ ``,
3882
+ ` oasis design chat ${artifactId} --message "<\u8FD9\u4E00\u8F6E\u8981\u5F15\u64CE\u505A\u4EC0\u4E48\uFF1A\u8BBE\u8BA1\u9700\u6C42 + \u771F\u5B9E\u6570\u636E>" --async`,
3883
+ ` oasis design chat ${artifactId} --turn continue --message "<\u8BFB\u5B8C\u4E0A\u4E00\u7248\u4EA7\u7269\u540E\uFF0C\u8981\u6539\u54EA\u91CC>" --async`,
3884
+ ``,
3885
+ `\u5F15\u64CE\u4E00\u8F6E\u8981 13\u201344 \u5206\u949F\uFF0C\u5728 od \u670D\u52A1\u7AEF\u81EA\u5DF1\u8DD1\u3001**\u4E0E\u4F60\u7684\u4F1A\u8BDD\u751F\u6B7B\u65E0\u5173**\u2014\u2014\`--async\` \u4E0B\u5355\u5373\u8FD4\uFF0C**\u4E0D\u8981\u81EA\u5DF1\u53D1\u660E\u963B\u585E\u7B49\u5F85\u3001\u66F4\u522B\u7528 \`timeout\` \u5305\u547D\u4EE4**\u3002\u4E0B\u5355\u540E\u6BCF\u9694\u51E0\u5206\u949F \`oasis design status\` \u770B\u4E00\u773C\uFF1B\u4E00\u76F4\u6CA1\u5B8C\u4E5F\u53EF\u4EE5\u6536\u5DE5\uFF0C\u4E0B\u6B21\u6D3E\u53D1\u5148 status \u53D6\u8D27\u3002`,
3886
+ ``,
3887
+ `**\u6BCF\u4E00\u8F6E\u7684\u95ED\u73AF\uFF08\u522B\u8DF3\u6B65\uFF09**\uFF1A`,
3888
+ `1. **\u5582\u9971\u5B83**\uFF1A\u9996\u8F6E \`--message\` \u628A\u8BBE\u8BA1\u9700\u6C42\u8FDE\u540C**\u771F\u5B9E\u793A\u4F8B\u6570\u636E**\u4E00\u8D77\u7ED9\u8DB3\u2014\u2014\u5F15\u64CE\u53EA\u62FF\u5230 \`DESIGN_BRIEF.md\`/\`inputs/\`\uFF0CPRD \u91CC\u6CA1\u6709\u7684 mock \u6570\u636E\u5B83\u4F1A**\u81EA\u5DF1\u7F16**\uFF08\u66FE\u628A\u5A74\u513F\u770B\u62A4\u5668\u7F16\u6210\u8033\u673A\uFF09\u3002\u771F\u5B9E\u4E1A\u52A1\u6570\u636E\uFF08\u4EA7\u54C1/\u4E70\u5BB6/\u8BA2\u5355\u7B49\uFF09\u5728\u54EA\u3001\u957F\u4EC0\u4E48\u6837\uFF0C\u4F60**\u5FC5\u987B\u5728 message \u91CC\u5199\u6E05\u6216\u544A\u8BC9\u5B83\u53BB\u54EA\u53D6**\uFF1B`,
3889
+ `2. **\u8BFB\u4EA7\u7269**\uFF1A\`oasis design status ${artifactId} --pull\` \u628A\u4EA4\u4ED8\u7269\u62C9\u5230\u672C\u5730\u2014\u2014**\u6253\u5F00 \`index.html\` \u771F\u8BFB\u4E00\u904D**\uFF0C\u5BF9\u7167 \`DESIGN_BRIEF.md\` \u4E0E \`inputs/\` \u6838\uFF1A\u6570\u636E\u5BF9\u4E0D\u5BF9\uFF1F\u72B6\u6001/\u8FB9\u754C\u6001\u5168\u4E0D\u5168\uFF1F\u6709\u6CA1\u6709\u7F16\u9020\uFF1F`,
3890
+ `3. **\u51B3\u5B9A**\uFF1A\u591F\u597D \u2192 \`oasis propose ${artifactId} --from-dir <\u62C9\u4E0B\u6765\u7684\u76EE\u5F55> --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"\` \u4EA4\u7A3F\uFF1B\u8FD8\u5DEE \u2192 \`--turn continue\` **\u518D\u4E00\u8F6E**\u6253\u78E8\uFF1B\u9047\u5230**\u4F60\u4E5F\u5B9A\u4E0D\u4E86\u7684\u53D6\u820D**\uFF08\u5982\u67D0\u6001\u7528\u54EA\u4E2A\u4E70\u5BB6\u627F\u8F7D\uFF09\u2192 \u5199\u8FDB acceptance.md \u7684\u300C\u5F00\u653E\u95EE\u9898\u300D\uFF0C\u6216 \`oasis gap ${artifactId} --desc "<\u8981\u4EBA\u5B9A\u4EC0\u4E48>"\` \u5192\u6CE1\u7ED9\u4EBA\uFF1B`,
3891
+ `- **\u522B\u628A\u7B2C\u4E00\u7248\u76F2\u76EE propose**\u2014\u2014\u5148\u8BFB\u3001\u5148\u81EA\u68C0\u3001\u4E0D\u591F\u5C31\u518D\u4E00\u8F6E\uFF0C\u6BD4 propose \u51FA\u53BB\u88AB\u4E0B\u6E38\u6253\u56DE\u3001\u7ED5\u6574\u5F20\u56FE\u4E00\u5708\u4FBF\u5B9C\u5F97\u591A\uFF1B`,
3892
+ `- \u7F3A**\u8FD8\u4E0D\u5B58\u5728\u7684\u4E0A\u6E38\u524D\u7F6E**\u505A\u4E0D\u4E0B\u53BB \u2192 \`oasis gap ${artifactId} --desc "<\u7F3A\u4EC0\u4E48>"\` \u62A5\u7F3A\u53E3\u540E\u6536\u5DE5\u3002`
3871
3893
  ] : (() => {
3872
3894
  const codeRepo = args.codeRepo ?? null;
3873
3895
  const repos = typeDef?.contentType === "code" ? codeRepo?.repos : void 0;
@@ -3922,7 +3944,7 @@ async function assembleContext(args) {
3922
3944
  ``,
3923
3945
  ...canvasMode ? [
3924
3946
  `## \u5173\u4E8E\u5DE5\u5177`,
3925
- `oasis CLI \u5DF2\u6CE8\u5165\uFF08\`OASIS_SERVER\` / \`OASIS_TOKEN\` \u5C31\u7EEA\uFF0C\u65E0\u9700\u914D\u7F6E\uFF09\u2014\u2014**\u4EA7\u51FA\u8FD9\u4E2A design \u4EA7\u7269\u5C31\u9760\u4E0A\u9762\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u91CC\u7684 \`oasis design run\`**\uFF08\u5B83\u53BB\u9A71\u52A8\u753B\u677F\u5F15\u64CE\u3001\u4EA7\u754C\u9762\u7A3F\u3001\u4EA4\u7A3F\uFF09\uFF1B\u7F3A\u524D\u7F6E\u7528 \`oasis gap\`\u3002\u8BBE\u8BA1\u9700\u6C42\u89C1\u672C\u76EE\u5F55 \`DESIGN_BRIEF.md\`\uFF0C\u4E0A\u6E38\u8F93\u5165\u5728 \`inputs/\`\u3002`
3947
+ `oasis CLI \u5DF2\u6CE8\u5165\uFF08\`OASIS_SERVER\` / \`OASIS_TOKEN\` \u5C31\u7EEA\uFF0C\u65E0\u9700\u914D\u7F6E\uFF09\u2014\u2014\u5148 \`oasis design status\` \u53D6\u8D27\uFF0C\u518D\u7528 \`oasis design chat --async\` \u591A\u8F6E\u9A71\u52A8\u753B\u677F\u5F15\u64CE\u6253\u78E8\u754C\u9762\u7A3F\uFF0C\u8BFB\u4EA7\u7269\u540E\u7528 \`oasis propose\` \u4EA4\u7A3F\u3001\`oasis gap\` \u62A5\u7F3A\u53E3\uFF08\u90FD\u89C1\u4E0A\u9762\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002\u8BBE\u8BA1\u9700\u6C42\u89C1\u672C\u76EE\u5F55 \`DESIGN_BRIEF.md\`\uFF0C\u4E0A\u6E38\u8F93\u5165\u5728 \`inputs/\`\u3002`
3926
3948
  ] : commandReference({
3927
3949
  artifactId,
3928
3950
  action: args.action ?? "produce",
@@ -3950,12 +3972,14 @@ async function assembleContext(args) {
3950
3972
  (s2) => `- ${s2.upstream}: pin=${s2.pinned ?? "\u2205"} \u2192 \u6700\u65B0 ${s2.latest}${s2.upstreamCancelled ? "\uFF08\u26A0 \u4E0A\u6E38\u5DF2 cancelled\uFF09" : ""}`
3951
3973
  )
3952
3974
  ] : [],
3953
- // 画板模式:守则的 oasis 命令(gap/annotate/propose/add-revision)内层引擎全不可用——换画板版守则
3975
+ // 画板模式:你在通用 runtime 上、CLI 齐全(design chat/propose/gap 都能用);引擎里的内层 agent 才没 CLI,那层不用你管
3954
3976
  ...canvasMode ? [
3955
3977
  ``,
3956
3978
  `## \u751F\u4EA7\u8005\u5B88\u5219\uFF08\u753B\u677F\u7248\uFF09`,
3957
- `- \u4F60\u53EA\u4EA7**\u8FD9\u4E00\u4E2A**\u8BBE\u8BA1\u4EA7\u7269\uFF1B**\u4E0D\u5F52\u4F60\u5B9A\u7684\u51B3\u7B56\u522B\u786C\u7F16**\uFF08\u4F1A\u88AB\u6253\u56DE\uFF09\u2014\u2014\u62FF\u4E0D\u51C6\u7684\u53D6\u820D\u5199\u8FDB acceptance.md \u7684\u300C\u5F00\u653E\u95EE\u9898\u300D\u3002`,
3979
+ `- \u4F60\u662F\u7532\u65B9\uFF1A\u5148 \`oasis design status\` \u53D6\u8D27\uFF08\u5F15\u64CE\u7684\u6D3B\u4E0D\u767D\u5E72\uFF09\uFF0C\u518D\u591A\u8F6E \`oasis design chat --async\` \u9A71\u52A8\u5F15\u64CE\uFF1B\u6BCF\u8F6E**\u8BFB\u4EA7\u7269**\u518D\u51B3\u5B9A\u2014\u2014\u591F\u597D\u624D \`oasis propose\`\uFF0C\u522B\u628A\u7B2C\u4E00\u7248\u76F2\u76EE\u4EA4\u51FA\u53BB\u3002`,
3980
+ `- \u4F60\u53EA\u4EA7**\u8FD9\u4E00\u4E2A**\u8BBE\u8BA1\u4EA7\u7269\uFF1B**\u4E0D\u5F52\u4F60\u5B9A\u7684\u51B3\u7B56\u522B\u786C\u7F16**\uFF08\u4F1A\u88AB\u6253\u56DE\uFF09\u2014\u2014\u62FF\u4E0D\u51C6\u7684\u53D6\u820D\u5199\u8FDB acceptance.md \u7684\u300C\u5F00\u653E\u95EE\u9898\u300D\uFF0C\u6216 \`oasis gap\` \u5192\u6CE1\u7ED9\u4EBA\u3002`,
3958
3981
  `- \u5FE0\u5B9E\u4E8E task-context/ \u91CC\u7684\u4E0A\u6E38\u8F93\u5165\uFF08PRD/\u603B\u7EB2\uFF09\uFF1B\u4E0E\u4E0A\u6E38\u51B2\u7A81\u5904\u4EE5\u4E0A\u6E38\u4E3A\u51C6\uFF0C\u5E76\u5728\u5F00\u653E\u95EE\u9898\u91CC\u6807\u6CE8\u5F02\u8BAE\u3002`,
3982
+ `- \u771F\u5B9E\u793A\u4F8B\u6570\u636E\uFF08\u4EA7\u54C1/\u4E70\u5BB6/\u8BA2\u5355\u7B49\uFF09**\u5FC5\u987B\u5728 \`--message\` \u91CC\u5582\u7ED9\u5F15\u64CE**\u2014\u2014\u5B83\u62FF\u4E0D\u5230\u7684\u4E1C\u897F\u4F1A\u81EA\u5DF1\u7F16\uFF0C\u522B\u8BA9\u5B83\u66FF\u4F60\u9020\u6570\u636E\u3002`,
3959
3983
  `- \u754C\u9762\u7A3F\u5FC5\u987B**\u771F\u5B9E\u53EF\u6253\u5F00**\uFF08\u81EA\u5305\u542B HTML\uFF09\uFF0C\u522B\u4EA4\u793A\u610F\u63CF\u8FF0\u3002`
3960
3984
  ] : [],
3961
3985
  // review 会话不产出、不该给生产者守则(它只投票,守则的 propose/conclude/gap/add-revision 全不适用)
@@ -4228,7 +4252,7 @@ var init_dispatcher = __esm({
4228
4252
  }
4229
4253
  };
4230
4254
  SEED_ROOT_SUCCESSFUL_PRODUCE_LIMIT = 5;
4231
- SELF_HEALING_EXITS = /* @__PURE__ */ new Set(["rate-limit"]);
4255
+ SELF_HEALING_EXITS = /* @__PURE__ */ new Set(["rate-limit", "session-not-found"]);
4232
4256
  CANVAS_RUNTIME_KIND = "open-design";
4233
4257
  NON_PRODUCE_FALLBACK_RUNTIME_KIND = "claude-code";
4234
4258
  Dispatcher = class {
@@ -4284,7 +4308,8 @@ var init_dispatcher = __esm({
4284
4308
  }
4285
4309
  /** ann: 讨论/回踢 resume 用:该 artifact 最近一次 produce/integrate 会话的 runtimeSessionId */
4286
4310
  retainedProduceSessionFor(artifactId) {
4287
- return decodeProduceSession(this.produceSessions.get(`produce:${artifactId}`))?.rtId;
4311
+ const rec = decodeProduceSession(this.produceSessions.get(`produce:${artifactId}`));
4312
+ return rec?.sessionMissing ? void 0 : rec?.rtId;
4288
4313
  }
4289
4314
  emitJournal(entry) {
4290
4315
  try {
@@ -4681,8 +4706,8 @@ var init_dispatcher = __esm({
4681
4706
  const sessionKey = this.produceSessionKeyFor(spec);
4682
4707
  const prior = sessionKey ? decodeProduceSession(this.produceSessions.get(sessionKey)) : void 0;
4683
4708
  const priorConsistent = prior !== void 0 && prior.nodeId === binding?.nodeId && prior.runtimeKind === binding?.runtimeKind;
4684
- const retainedPartSession = spec.action === "produce" && spec.part !== void 0 && priorConsistent ? prior.rtId : void 0;
4685
- const retainedWholeSession = spec.action === "produce" && spec.part === void 0 && priorConsistent ? this.resumableWholeSession(spec.artifactId, seqNow, prior.rtId) : void 0;
4709
+ const retainedPartSession = spec.action === "produce" && spec.part !== void 0 && priorConsistent && !prior.sessionMissing ? prior.rtId : void 0;
4710
+ const retainedWholeSession = spec.action === "produce" && spec.part === void 0 && priorConsistent && !prior.sessionMissing ? this.resumableWholeSession(spec.artifactId, seqNow, prior.rtId) : void 0;
4686
4711
  const retainedSession = retainedPartSession ?? retainedWholeSession;
4687
4712
  const runtimeSessionId = retainedSession ?? (isProduceLike ? (0, import_node_crypto3.randomUUID)() : void 0);
4688
4713
  const resumeSession = retainedSession !== void 0;
@@ -4856,11 +4881,12 @@ var init_dispatcher = __esm({
4856
4881
  const prev = decodeProduceSession(this.produceSessions.get(exitSessionKey));
4857
4882
  if (prev) {
4858
4883
  const isWholeProduce = spec.action === "produce" && spec.part === void 0;
4884
+ const sessionNotFound = info.reason === "session-not-found";
4859
4885
  try {
4860
4886
  this.produceSessions.set(exitSessionKey, encodeProduceSession({
4861
4887
  ...prev,
4862
4888
  ...info.runtimeSessionId ? { rtId: info.runtimeSessionId } : {},
4863
- ...isWholeProduce ? { seqAtExit } : { seqAtExit: void 0 },
4889
+ ...sessionNotFound ? { sessionMissing: true, seqAtExit: void 0 } : { sessionMissing: void 0, ...isWholeProduce ? { seqAtExit } : { seqAtExit: void 0 } },
4864
4890
  lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
4865
4891
  }));
4866
4892
  } catch {
@@ -136048,8 +136074,22 @@ var init_planner = __esm({
136048
136074
  name: "provision",
136049
136075
  contentType: "text",
136050
136076
  ownerRole: "devops",
136051
- description: "\u4EE3\u7801\u4ED3\u5E93\u4F9B\u7ED9\uFF08\u5EFA\u4ED3\uFF09\uFF1A\u5F53\u9879\u76EE\u91CC\u6CA1\u6709\u4E0B\u6E38\u5B9E\u73B0\u6240\u9700\u7684\u4EE3\u7801\u4ED3\u5E93\u65F6\uFF0C\u7528\u6B64\u8282\u70B9\u5EFA\u4ED3\u2014\u2014\u6309\u7ED3\u6784\uFF08\u5355\u4ED3/\u4F1E\u4ED3\uFF09\u4E0E\u547D\u540D\u5EFA\u597D\u4ED3\u5E93\uFF0C\u521D\u59CB\u5316\u9879\u76EE\u6846\u67B6\u3001\u5FC5\u5907\u6587\u6863\u3001CI/CD \u57FA\u5EFA\u4E0E dev/prod \u73AF\u5883\u914D\u7F6E\uFF0C\u5E76\u767B\u8BB0\u5230\u9879\u76EE\u3002**\u5EFA\u56FE/\u8865\u56FE\u65F6\uFF0C\u82E5\u53D1\u73B0\u9879\u76EE\u7F3A\u67D0\u4E2A dev\uFF08\u4EE3\u7801\u5B9E\u73B0\uFF09\u8282\u70B9\u6240\u9700\u7684\u4EE3\u7801\u4ED3\u5E93\uFF0C\u5E94\u65B0\u589E\u4E00\u4E2A\u6B64\u7C7B\u578B\u8282\u70B9\uFF0C\u4F5C\u4E3A\u90A3\u4E2A dev \u8282\u70B9\u7684\u524D\u7F6E\u3001\u6392\u5728\u5B83\u4E4B\u524D\u3002** \u4EA7\u51FA\u4E3A\u5EFA\u4ED3\u8BB0\u5F55\uFF08\u8BF4\u660E\u5EFA\u4E86\u54EA\u4E9B\u4ED3\u3001\u903B\u8F91 id \u4E0E\u7ED3\u6784\uFF09\uFF0C\u67B6\u6784\u5E08\u8BC4\u5BA1\u3002",
136052
- reviews: { gates: [{ on: "conclude", reviewers: ["architect"], quorum: 1 }] }
136077
+ description: "\u4EE3\u7801\u4ED3\u5E93\u4F9B\u7ED9\uFF08\u5EFA\u4ED3\uFF09\uFF1A\u5F53\u9879\u76EE\u91CC\u6CA1\u6709\u4E0B\u6E38\u5B9E\u73B0\u6240\u9700\u7684\u4EE3\u7801\u4ED3\u5E93\u65F6\uFF0C\u7528\u6B64\u8282\u70B9\u5EFA\u4ED3\u2014\u2014\u6309\u7ED3\u6784\uFF08\u5355\u4ED3/\u4F1E\u4ED3\uFF09\u4E0E\u547D\u540D\u5EFA\u597D\u4ED3\u5E93\uFF0C\u521D\u59CB\u5316\u9879\u76EE\u6846\u67B6\u3001\u5FC5\u5907\u6587\u6863\u3001CI/CD \u57FA\u5EFA\uFF08GitHub Actions ci/cd workflow\uFF09\u4E0E dev/prod \u73AF\u5883\u914D\u7F6E\uFF0C\u5E76**\u767B\u8BB0\u5230\u9879\u76EE\u4E14\u6838\u9A8C\u767B\u8BB0\u771F\u7684\u843D\u8FDB\u5185\u6838**\uFF08\u4E0D\u662F\u5199\u7BC7\u6587\u6863\u8BF4\u914D\u597D\u4E86\u5C31\u7B97\uFF09\u3002**\u5EFA\u56FE/\u8865\u56FE\u65F6\uFF0C\u82E5\u53D1\u73B0\u9879\u76EE\u7F3A\u67D0\u4E2A dev\uFF08\u4EE3\u7801\u5B9E\u73B0\uFF09\u8282\u70B9\u6240\u9700\u7684\u4EE3\u7801\u4ED3\u5E93\uFF0C\u5E94\u65B0\u589E\u4E00\u4E2A\u6B64\u7C7B\u578B\u8282\u70B9\uFF0C\u4F5C\u4E3A\u90A3\u4E2A dev \u8282\u70B9\u7684\u524D\u7F6E\u3001\u6392\u5728\u5B83\u4E4B\u524D\u3002** \u4EA7\u51FA\u4E3A\u5EFA\u4ED3\u8BB0\u5F55\uFF08\u8BF4\u660E\u5EFA\u4E86\u54EA\u4E9B\u4ED3\u3001\u903B\u8F91 id \u4E0E\u7ED3\u6784\u3001CI/CD \u9AA8\u67B6\uFF09\uFF0C\u67B6\u6784\u5E08\u8BC4\u5BA1\u3002\u6536\u5C3E\u524D\u987B `oasis record-action <\u672C\u8282\u70B9> --name registration_verified --pass true` \u7559\u75D5\uFF08\u5148\u771F\u6838\u9A8C\u767B\u8BB0\u843D\u5E93\uFF09\u3002",
136078
+ reviews: { gates: [{ on: "conclude", reviewers: ["architect"], quorum: 1 }] },
136079
+ // grounding:登记必须真落库才收口(ADR-0070/§9.2)。防「写篇文档说仓配好了就算交付」(delivery C-2)。
136080
+ grounding: { requiredActions: [{ name: "registration_verified", mustPass: true }] }
136081
+ },
136082
+ {
136083
+ // deploy 此前只在生产经 API 手工新增、不在代码常量里(零审计,见 ADR-0067 背景与 ADR-0070)。
136084
+ // 收编进 DEFAULT_TYPES:给它一份规范定义作为新环境/测试/文档的事实源。
136085
+ // 注意 ensureDefaultWorkorderTypes 只“补缺”——生产已存在的 deploy 条目不会被此定义覆盖。
136086
+ name: "deploy",
136087
+ contentType: "text",
136088
+ ownerRole: "devops",
136089
+ description: "\u771F\u5B9E\u90E8\u7F72\u4E0E\u8054\u8C03\uFF1A\u628A\u5BF9\u5E94 dev\uFF08\u4EE3\u7801\u5B9E\u73B0\uFF09\u4EA7\u51FA\u90E8\u7F72\u5230\u76EE\u6807\u73AF\u5883\u5E76\u8DD1\u901A\u7AEF\u5230\u7AEF\u8054\u8C03\uFF0C\u4EA7\u51FA\u53EF\u8BBF\u95EE\u7684\u90E8\u7F72\u5730\u5740 + \u90E8\u7F72\u8BB0\u5F55\uFF08\u73AF\u5883/\u670D\u52A1/\u7AEF\u53E3/\u5065\u5EB7\u68C0\u67E5\u8BC1\u636E\uFF09\u3002**\u4F9D\u8D56\u5BF9\u5E94\u7684 dev \u8282\u70B9\uFF08required\uFF09\uFF0C\u6392\u5728\u5B83\u4E4B\u540E\uFF1B\u82E5\u672C\u5DE5\u5355\u6709 adr\uFF08\u6280\u672F\u65B9\u6848\uFF0C\u542B CI/CD \u4E0E\u90E8\u7F72\u73AF\u5883\u51B3\u7B56\uFF09\uFF0C\u4E5F\u5E94\u4F9D\u8D56\u8BE5 adr\u2014\u2014\u5982\u6B64\u6280\u672F\u65B9\u6848\u6216\u4EE3\u7801\u4E00\u51FA\u65B0\u91CC\u7A0B\u7891\uFF0C\u672C\u8282\u70B9\u5373\u88AB\u7EA7\u8054\u8FD4\u5DE5\u91CD\u65B0\u90E8\u7F72\uFF08ADR-0067\uFF09\u3002** \u5EFA\u56FE/\u8865\u56FE\u65F6\uFF0C\u5F53\u5DE5\u5355\u9700\u8981\u771F\u90E8\u7F72/\u4E0A\u7EBF\u800C\u975E\u4EC5\u4EA4\u4ED8\u4EE3\u7801\u65F6\u65B0\u589E\u6B64\u8282\u70B9\u3002\u5065\u5EB7\u68C0\u67E5\u987B\u5916\u90E8\u53EF\u8FBE\u3001\u7981\u5047\u7EFF\uFF1B\u4E0B\u6E38\uFF08\u5982 QA\uFF09\u53D1\u73B0\u90E8\u7F72\u7ED3\u679C\u4E0D\u5BF9\u65F6\u5BF9\u672C\u8282\u70B9\u56DE\u538B\uFF08annotate --from\uFF09\u3002\u6536\u5C3E\u524D\u987B\u5148\u771F\u8DD1\u5916\u90E8\u5065\u5EB7\u63A2\u9488\u3001\u518D `oasis record-action <\u672C\u8282\u70B9> --name health_probe --pass true` \u7559\u75D5\uFF0C\u5426\u5219\u6536\u4E0D\u4E86\u53E3\u3002",
136090
+ reviews: { gates: [{ on: "conclude", reviewers: ["qa"], quorum: 1 }] },
136091
+ // grounding:必须有「通过的健康探针」留痕才收口(ADR-0070/§9.2)。机械堵假绿——force 也豁免不了。
136092
+ grounding: { requiredActions: [{ name: "health_probe", mustPass: true }] }
136053
136093
  }
136054
136094
  ];
136055
136095
  WORKORDER_SPEC_PREFIX = "OASIS_WORKORDER_SPEC ";
@@ -137781,11 +137821,15 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
137781
137821
  }
137782
137822
  case "conclude": {
137783
137823
  const artifactId = str(args, "artifactId");
137784
- await kernel.conclude({ artifactId, actor, note: optStr(args, "note") });
137785
- const pending = pendingConcludeAttempt(kernel.model, artifactId);
137786
- return {
137787
- message: pending ? `conclude \u5DF2\u53D1\u8D77\uFF0Cgate \u5F85\u5BA1\uFF08quorum=${pending.gate.quorum}\uFF0Celigible=${pending.gate.eligible.join(", ")}\uFF09` : `concluded\uFF1A\u91CC\u7A0B\u7891\u6210\u7ACB\uFF08head=${concludedHead(kernel.model, artifactId)}\uFF09`
137788
- };
137824
+ const result = await kernel.conclude({ artifactId, actor, note: optStr(args, "note") });
137825
+ if (result.kind === "gate-pending") {
137826
+ const pending = pendingConcludeAttempt(kernel.model, artifactId);
137827
+ return { message: `conclude \u5DF2\u53D1\u8D77\uFF0Cgate \u5F85\u5BA1\uFF08quorum=${pending.gate.quorum}\uFF0Celigible=${pending.gate.eligible.join(", ")}\uFF09` };
137828
+ }
137829
+ if (result.kind === "noop-concluded") {
137830
+ return { message: `\u8BE5 head \u5DF2\u7ECF\u6536\u53E3\u8FC7\u4E86\u3001\u65E0\u65B0\u7248\u672C\u5F85\u6536\u53E3\uFF08head=${result.head}\uFF09\u2014\u2014\u82E5\u4F60\u521A propose \u4E86\u65B0\u7248\uFF0C\u5B83\u53EF\u80FD\u8FD8\u5728\u961F\u5217/\u8BC4\u5BA1\u4E2D\uFF0C\u5148\u8BA9\u5B83\u6210\u4E3A head \u518D\u6536\u53E3\u3002` };
137831
+ }
137832
+ return { message: `concluded\uFF1A\u91CC\u7A0B\u7891\u6210\u7ACB\uFF08head=${result.head}\uFF09` };
137789
137833
  }
137790
137834
  case "review": {
137791
137835
  const revisionArg = optStr(args, "revisionId");
@@ -138416,7 +138460,7 @@ async function startOasisServer(opts) {
138416
138460
  res.writeHead(status, { "content-type": "application/json" }).end(
138417
138461
  JSON.stringify({
138418
138462
  error: err instanceof Error ? err.message : String(err),
138419
- ...err instanceof FrozenError ? { retryable: true } : {}
138463
+ ...err instanceof FrozenError ? { retryable: err.retryable } : {}
138420
138464
  })
138421
138465
  );
138422
138466
  }
@@ -139078,10 +139122,13 @@ async function startOasisServer(opts) {
139078
139122
  ...draftWorkspace ? { draftWorkspace } : {}
139079
139123
  });
139080
139124
  const effectiveSessionId = persistTarget ? runtimeChanged ? void 0 : persistTarget.runtimeSessionId ?? void 0 : body.sessionId || void 0;
139125
+ const resumeFallbackHistory = effectiveSessionId && chatStore && persistTarget ? (await chatStore.listMessages(persistTarget.id, 20)).filter((m2) => !(m2.role === "assistant" && m2.status === "running")).map((m2) => ({ role: m2.role, content: m2.role === "user" ? stripInjectedChatContext(m2.content) : m2.content })) : [];
139126
+ const fallbackMessage = resumeFallbackHistory.length ? buildRuntimeChatPrompt({ userMessage: persistedUserMessage, history: resumeFallbackHistory, ...draftWorkspace ? { draftWorkspace } : {} }) : void 0;
139081
139127
  const session = await opts.dispatchChat({
139082
139128
  actorId: body.actorId,
139083
139129
  message: runtimeMessage,
139084
139130
  ...effectiveSessionId ? { sessionId: effectiveSessionId } : {},
139131
+ ...fallbackMessage ? { fallbackMessage } : {},
139085
139132
  ...body.chatSessionId ? { chatSessionId: body.chatSessionId } : {},
139086
139133
  ...body.workspace ? { workspace: body.workspace } : {},
139087
139134
  ...currentCompanyId !== void 0 ? { companyId: currentCompanyId } : {},
@@ -139291,8 +139338,8 @@ async function startOasisServer(opts) {
139291
139338
  if (actorCtx) {
139292
139339
  const { mkdtempSync: mkdtempSync5, mkdirSync: mkdirSync17, writeFileSync: writeFileSync15 } = await import("node:fs");
139293
139340
  const { join: join24, dirname: dirname20 } = await import("node:path");
139294
- const { tmpdir: tmpdir8 } = await import("node:os");
139295
- const dir = mkdtempSync5(join24(tmpdir8(), "oasis-chat-"));
139341
+ const { tmpdir: tmpdir9 } = await import("node:os");
139342
+ const dir = mkdtempSync5(join24(tmpdir9(), "oasis-chat-"));
139296
139343
  if (actorCtx.config?.prompt) {
139297
139344
  for (const [rel, content] of Object.entries(splitIdentityFiles2(actorCtx.config.prompt))) {
139298
139345
  const file = join24(dir, rel);
@@ -143591,6 +143638,7 @@ async function seedRoleCatalog(store, src) {
143591
143638
  await store.put({
143592
143639
  name,
143593
143640
  label: known?.label ?? fallbackLabel(name),
143641
+ ...known?.description ? { description: known.description } : {},
143594
143642
  ...known?.accent ? { appearance: { accent: known.accent } } : {},
143595
143643
  status: "active"
143596
143644
  });
@@ -143605,13 +143653,17 @@ var init_roles_seed = __esm({
143605
143653
  "use strict";
143606
143654
  KNOWN = {
143607
143655
  founder: { label: "\u521B\u59CB\u4EBA", accent: "slate" },
143608
- pm: { label: "\u9700\u6C42\u5206\u6790", accent: "blue" },
143609
- architect: { label: "\u67B6\u6784\u8BBE\u8BA1", accent: "violet" },
143610
- designer: { label: "\u754C\u9762\u8BBE\u8BA1", accent: "fuchsia" },
143611
- research: { label: "\u5E02\u573A\u8C03\u7814", accent: "cyan" },
143612
- dev: { label: "\u4EE3\u7801\u5B9E\u73B0", accent: "emerald" },
143613
- qa: { label: "\u8D28\u91CF\u4FDD\u969C", accent: "rose" },
143614
- devops: { label: "\u8FD0\u7EF4\u90E8\u7F72", accent: "amber" },
143656
+ pm: { label: "\u9700\u6C42\u5206\u6790", accent: "blue", description: "\u628A\u8BC9\u6C42\u62C6\u6210 PRD \u4E0E\u9A8C\u6536\u6807\u51C6\uFF0C\u505A\u4EA7\u54C1\u51B3\u7B56\u4E0E\u4F18\u5148\u7EA7\u3002" },
143657
+ architect: { label: "\u67B6\u6784\u8BBE\u8BA1", accent: "violet", description: "\u6280\u672F\u9009\u578B\u3001\u7CFB\u7EDF\u67B6\u6784\u3001\u63A5\u53E3\u5951\u7EA6\u3001ADR\uFF1B\u628A\u6280\u672F\u65B9\u6848\u843D\u6210\u53EF\u5B9E\u73B0\u7684\u51B3\u7B56\u3002" },
143658
+ designer: { label: "\u754C\u9762\u8BBE\u8BA1", accent: "fuchsia", description: "\u754C\u9762\u4E0E\u4EA4\u4E92\u8BBE\u8BA1\u3001\u8BBE\u8BA1\u7CFB\u7EDF\u4E0E\u89C6\u89C9\u89C4\u8303\u3002" },
143659
+ research: { label: "\u5E02\u573A\u8C03\u7814", accent: "cyan", description: "\u7ADE\u54C1\u3001\u5E02\u573A\u3001\u7528\u6237\u4E0E\u5B9A\u4EF7\u7B49\u8C03\u7814\u3002" },
143660
+ dev: { label: "\u4EE3\u7801\u5B9E\u73B0", accent: "emerald", description: "\u6309\u63A5\u53E3\u5951\u7EA6\u5B9E\u73B0\u524D\u540E\u7AEF\u4EE3\u7801\u3002" },
143661
+ qa: { label: "\u8D28\u91CF\u4FDD\u969C", accent: "rose", description: "\u6D4B\u8BD5\u89C4\u5212\u3001\u771F\u673A\u9A8C\u6536\u4E0E\u56DE\u5F52\uFF1B\u53D1\u73B0\u95EE\u9898\u56DE\u538B\u5BF9\u5E94\u4EA7\u51FA\u8282\u70B9\u3002" },
143662
+ devops: {
143663
+ label: "\u8FD0\u7EF4\u90E8\u7F72",
143664
+ accent: "amber",
143665
+ description: "\u5E73\u53F0\u5DE5\u7A0B\uFF1A\u4EE3\u7801\u4ED3\u5E93\u4F9B\u7ED9\uFF08\u5EFA\u4ED3\uFF09\u3001CI/CD \u6D41\u6C34\u7EBF\u4E0E GitHub Actions\u3001\u7EC4\u7EC7\u8D44\u4EA7\u53F0\u8D26\u7EF4\u62A4\uFF0C\u4EE5\u53CA\u771F\u5B9E\u90E8\u7F72\u4E0E\u8054\u8C03\uFF1B\u5176\u5B83\u73AF\u8282\u51FA\u73B0\u90E8\u7F72/\u8FD0\u7EF4\u7C7B\u95EE\u9898\u65F6\u515C\u5E95\u3002\u8D1F\u8D23 provision \u4E0E deploy \u4EA4\u4ED8\u7269\u7C7B\u578B\u3002"
143666
+ },
143615
143667
  writer: { label: "\u6587\u6863\u64B0\u5199", accent: "slate" },
143616
143668
  "design-lead": { label: "\u8BBE\u8BA1\u8D1F\u8D23\u4EBA", accent: "fuchsia" },
143617
143669
  "eng-lead": { label: "\u6280\u672F\u8D1F\u8D23\u4EBA", accent: "violet" }
@@ -146742,6 +146794,17 @@ function materializeContext(resolvedDir, files) {
146742
146794
  fs8.writeFileSync(f2, content);
146743
146795
  }
146744
146796
  }
146797
+ async function putVisibleUserTurn(base, fetchImpl, args) {
146798
+ const messageId = (0, import_node_crypto11.randomUUID)();
146799
+ await odApi(
146800
+ base,
146801
+ fetchImpl,
146802
+ "PUT",
146803
+ `/api/projects/${encodeURIComponent(args.boardKey)}/conversations/${encodeURIComponent(args.conversationId)}/messages/${messageId}`,
146804
+ { id: messageId, role: "user", content: args.content }
146805
+ );
146806
+ return messageId;
146807
+ }
146745
146808
  async function startDesignRun(base, fetchImpl, args) {
146746
146809
  const created = await odApi(base, fetchImpl, "POST", "/api/runs", {
146747
146810
  agentId: args.agentId,
@@ -146750,10 +146813,55 @@ async function startDesignRun(base, fetchImpl, args) {
146750
146813
  assistantMessageId: (0, import_node_crypto11.randomUUID)(),
146751
146814
  clientRequestId: (0, import_node_crypto11.randomUUID)(),
146752
146815
  message: args.message,
146753
- systemPrompt: args.systemPrompt
146816
+ // 提案 design-conversational-driving 坑②:systemPrompt 从写死改为可选——缺省用引擎侧默认,
146817
+ // 让设计意图由驱动方每轮的话(message)表达,而非焊死一句。
146818
+ ...args.systemPrompt !== void 0 ? { systemPrompt: args.systemPrompt } : {}
146754
146819
  });
146755
146820
  return created.runId;
146756
146821
  }
146822
+ async function fetchResultPackage(base, fetchImpl, runId) {
146823
+ const body = await odApi(base, fetchImpl, "GET", `/api/runs/${encodeURIComponent(runId)}/result-package`);
146824
+ const artifacts = (body.artifacts ?? []).map((a) => ({
146825
+ path: a.path ?? a.file ?? "",
146826
+ kind: a.kind ?? null,
146827
+ title: a.title ?? null,
146828
+ status: a.status ?? null
146829
+ }));
146830
+ return { artifacts, fileCount: body.project?.fileCount ?? artifacts.length };
146831
+ }
146832
+ async function fetchBoardStatus(base, fetchImpl, artifactId) {
146833
+ const boardKey = deriveBoardKey(artifactId);
146834
+ let boardExists = true;
146835
+ let artifacts = [];
146836
+ try {
146837
+ const listing = await odApi(
146838
+ base,
146839
+ fetchImpl,
146840
+ "GET",
146841
+ `/api/projects/${encodeURIComponent(boardKey)}/files`
146842
+ );
146843
+ artifacts = (listing.files ?? []).filter((f2) => !f2.path.startsWith("task-context/") && f2.artifactManifest !== void 0).map((f2) => ({ path: f2.path, kind: f2.kind ?? null, status: f2.artifactManifest?.status ?? null, mtime: f2.mtime ?? null })).sort((a, b2) => (b2.mtime ?? 0) - (a.mtime ?? 0));
146844
+ } catch {
146845
+ boardExists = false;
146846
+ }
146847
+ let runs = [];
146848
+ if (boardExists) {
146849
+ try {
146850
+ const body = await odApi(
146851
+ base,
146852
+ fetchImpl,
146853
+ "GET",
146854
+ `/api/runs?projectId=${encodeURIComponent(boardKey)}`
146855
+ );
146856
+ runs = (body.runs ?? []).map((r) => ({
146857
+ runId: String(r["runId"] ?? r["id"] ?? ""),
146858
+ status: String(r["status"] ?? "unknown")
146859
+ }));
146860
+ } catch {
146861
+ }
146862
+ }
146863
+ return { boardKey, boardExists, runs, artifacts };
146864
+ }
146757
146865
  async function pumpDesignEvents(base, fetchImpl, runId, onLine) {
146758
146866
  const res = await fetchImpl(`${base}/api/runs/${runId}/events`);
146759
146867
  if (!res.ok || !res.body) return;
@@ -146807,11 +146915,16 @@ async function harvestBoard(base, fetchImpl, publicBase, workRoot, boardKey, run
146807
146915
  if (!res.ok) throw new Error(`od export ${rel} \u2192 ${res.status}`);
146808
146916
  return await res.text();
146809
146917
  };
146918
+ const fetchRaw = async (rel) => {
146919
+ const res = await fetchImpl(`${base}/api/projects/${encodeURIComponent(boardKey)}/raw/${rel.split("/").map(encodeURIComponent).join("/")}`);
146920
+ if (!res.ok) throw new Error(`od raw ${rel} \u2192 ${res.status}`);
146921
+ return await res.text();
146922
+ };
146810
146923
  fs8.writeFileSync(path7.join(dir, "index.html"), await fetchInline(entry));
146811
146924
  for (const f2 of listing.files ?? []) {
146812
146925
  if (f2.path.endsWith(".md") && !f2.path.includes("/")) {
146813
146926
  try {
146814
- fs8.writeFileSync(path7.join(dir, f2.path), await fetchInline(f2.path));
146927
+ fs8.writeFileSync(path7.join(dir, f2.path), await fetchRaw(f2.path));
146815
146928
  } catch {
146816
146929
  }
146817
146930
  }
@@ -146837,7 +146950,7 @@ async function runOpenDesignSession(opts) {
146837
146950
  boardKey,
146838
146951
  conversationId,
146839
146952
  message: opts.message,
146840
- systemPrompt: opts.systemPrompt
146953
+ ...opts.systemPrompt !== void 0 ? { systemPrompt: opts.systemPrompt } : {}
146841
146954
  });
146842
146955
  if (opts.onLine) void pumpDesignEvents(opts.base, fetchImpl, runId, opts.onLine).catch(() => {
146843
146956
  });
@@ -146850,6 +146963,35 @@ async function runOpenDesignSession(opts) {
146850
146963
  const { dir, entry } = await harvestBoard(opts.base, fetchImpl, opts.publicBase, workRoot, boardKey, runId);
146851
146964
  return { dir, entry, boardKey, runId };
146852
146965
  }
146966
+ async function startDesignChatTurn(opts) {
146967
+ const fetchImpl = opts.fetchImpl ?? fetch;
146968
+ const boardKey = deriveBoardKey(opts.artifactId);
146969
+ const { conversationId, resolvedDir } = await ensureProject(opts.base, fetchImpl, boardKey, opts.title);
146970
+ if (opts.contextFiles) materializeContext(resolvedDir, opts.contextFiles);
146971
+ const userMessageId = await putVisibleUserTurn(opts.base, fetchImpl, { boardKey, conversationId, content: opts.message });
146972
+ const runId = await startDesignRun(opts.base, fetchImpl, {
146973
+ agentId: opts.agentId,
146974
+ boardKey,
146975
+ conversationId,
146976
+ message: opts.message,
146977
+ ...opts.systemPrompt !== void 0 ? { systemPrompt: opts.systemPrompt } : {}
146978
+ });
146979
+ return { boardKey, conversationId, runId, userMessageId };
146980
+ }
146981
+ async function runDesignChatTurn(opts) {
146982
+ const fetchImpl = opts.fetchImpl ?? fetch;
146983
+ const { boardKey, conversationId, runId, userMessageId } = await startDesignChatTurn(opts);
146984
+ if (opts.onLine) void pumpDesignEvents(opts.base, fetchImpl, runId, opts.onLine).catch(() => {
146985
+ });
146986
+ const status = await pollDesignRun(opts.base, fetchImpl, runId, {
146987
+ ...opts.pollIntervalMs !== void 0 ? { intervalMs: opts.pollIntervalMs } : {},
146988
+ ...opts.pollMaxMs !== void 0 ? { maxMs: opts.pollMaxMs } : {}
146989
+ });
146990
+ if (status === "canceled") throw new Error(`od run ${runId} \u88AB\u53D6\u6D88`);
146991
+ if (status === "failed") throw new Error(`od run ${runId} \u5931\u8D25`);
146992
+ const { artifacts, fileCount } = await fetchResultPackage(opts.base, fetchImpl, runId);
146993
+ return { boardKey, conversationId, runId, userMessageId, artifacts, fileCount };
146994
+ }
146853
146995
  var import_node_crypto11, fs8, os, path7;
146854
146996
  var init_driver = __esm({
146855
146997
  "../adapters/src/open-design/driver.ts"() {
@@ -146892,6 +147034,25 @@ var init_sync_skills = __esm({
146892
147034
  }
146893
147035
  });
146894
147036
 
147037
+ // ../adapters/src/_core/resume-classify.ts
147038
+ function isSessionNotFoundError(text) {
147039
+ if (!text) return false;
147040
+ const t = text.toLowerCase();
147041
+ return SESSION_NOT_FOUND_PATTERNS.some((p2) => t.includes(p2));
147042
+ }
147043
+ var SESSION_NOT_FOUND_PATTERNS;
147044
+ var init_resume_classify = __esm({
147045
+ "../adapters/src/_core/resume-classify.ts"() {
147046
+ "use strict";
147047
+ SESSION_NOT_FOUND_PATTERNS = [
147048
+ "no conversation found",
147049
+ // claude: `claude --resume <缺失的 id>`
147050
+ "no rollout found for thread id"
147051
+ // codex: `codex exec resume <缺失的 uuid>`
147052
+ ];
147053
+ }
147054
+ });
147055
+
146895
147056
  // ../adapters/src/_core/session-paths.ts
146896
147057
  function resolveWorkRoot(workRoot) {
146897
147058
  if (workRoot) return workRoot;
@@ -147219,29 +147380,17 @@ function classifyExit(result) {
147219
147380
  if (result.is_error === true || String(result.subtype ?? "").startsWith("error")) return "error";
147220
147381
  return void 0;
147221
147382
  }
147222
- function conversationExists(sessionId) {
147223
- const configDir = process.env["CLAUDE_CONFIG_DIR"] || path9.join(os3.homedir(), ".claude");
147224
- const projectsDir = path9.join(configDir, "projects");
147225
- const target = `${sessionId}.jsonl`;
147226
- try {
147227
- for (const proj of fs10.readdirSync(projectsDir)) {
147228
- if (fs10.existsSync(path9.join(projectsDir, proj, target))) return true;
147229
- }
147230
- } catch {
147231
- }
147232
- return false;
147233
- }
147234
- var import_node_child_process7, import_node_crypto13, fs10, os3, path9, readline, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
147383
+ var import_node_child_process7, import_node_crypto13, fs10, path9, readline, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
147235
147384
  var init_claude_code = __esm({
147236
147385
  "../adapters/src/claude-code/index.ts"() {
147237
147386
  "use strict";
147238
147387
  import_node_child_process7 = require("node:child_process");
147239
147388
  import_node_crypto13 = require("node:crypto");
147240
147389
  fs10 = __toESM(require("node:fs"), 1);
147241
- os3 = __toESM(require("node:os"), 1);
147242
147390
  path9 = __toESM(require("node:path"), 1);
147243
147391
  readline = __toESM(require("node:readline"), 1);
147244
147392
  init_sync_skills();
147393
+ init_resume_classify();
147245
147394
  init_session_paths();
147246
147395
  ONE_SHOT_UNSAFE_TOOLS = ["ScheduleWakeup", "CronCreate", "CronDelete", "CronList"];
147247
147396
  ClaudeCodeAdapter = class {
@@ -147315,8 +147464,7 @@ var init_claude_code = __esm({
147315
147464
  const prompt = job.bundle.files["TASK.md"];
147316
147465
  if (!prompt) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
147317
147466
  const promptViaStdin = Buffer.byteLength(prompt, "utf8") > 1e5;
147318
- const canResume = job.resumeRuntimeSession && conversationExists(job.runtimeSessionId ?? "");
147319
- const runtimeSessionArgs = job.runtimeSessionId ? canResume ? ["--resume", job.runtimeSessionId] : ["--session-id", job.runtimeSessionId] : [];
147467
+ const runtimeSessionArgs = job.runtimeSessionId ? job.resumeRuntimeSession ? ["--resume", job.runtimeSessionId] : ["--session-id", job.runtimeSessionId] : [];
147320
147468
  const oneShotSafety = this.opts.oneShotSafety !== false;
147321
147469
  const args = [
147322
147470
  "-p",
@@ -147441,7 +147589,12 @@ var init_claude_code = __esm({
147441
147589
  child.on("exit", (code) => {
147442
147590
  clearTimeout(timer);
147443
147591
  if (exited) return;
147444
- const reason = killReason ?? classifyExit(lastResult);
147592
+ let reason = killReason ?? classifyExit(lastResult);
147593
+ const resultErrors = Array.isArray(lastResult?.errors) ? lastResult.errors.map(String) : [];
147594
+ const resumeErrText = [stderrTail.join("\n"), String(lastResult?.result ?? ""), ...resultErrors].join("\n");
147595
+ if (!killReason && job.resumeRuntimeSession && reason !== "clean" && isSessionNotFoundError(resumeErrText)) {
147596
+ reason = "session-not-found";
147597
+ }
147445
147598
  const workdirRef = dir;
147446
147599
  const isOneShot = job.workdirKey ? false : !job.runtimeSessionId;
147447
147600
  unlockWorkdir(dir);
@@ -147652,7 +147805,7 @@ var init_resilient = __esm({
147652
147805
 
147653
147806
  // ../adapters/src/_core/skill-cache.ts
147654
147807
  function defaultCacheRoot() {
147655
- return path10.join(os4.homedir(), ".oasis", "skill-cache");
147808
+ return path10.join(os3.homedir(), ".oasis", "skill-cache");
147656
147809
  }
147657
147810
  function sessionSkillsSubdir(runtimeKind) {
147658
147811
  switch (runtimeKind) {
@@ -147895,12 +148048,12 @@ async function syncSessionSymlinks(sessionDir, runtimeKind, actorDir, manifest,
147895
148048
  }
147896
148049
  }
147897
148050
  }
147898
- var fsp, os4, path10, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
148051
+ var fsp, os3, path10, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
147899
148052
  var init_skill_cache = __esm({
147900
148053
  "../adapters/src/_core/skill-cache.ts"() {
147901
148054
  "use strict";
147902
148055
  fsp = __toESM(require("node:fs/promises"), 1);
147903
- os4 = __toESM(require("node:os"), 1);
148056
+ os3 = __toESM(require("node:os"), 1);
147904
148057
  path10 = __toESM(require("node:path"), 1);
147905
148058
  MANIFEST_SCHEMA_VERSION = 1;
147906
148059
  STALE_LOCK_MS = 6e4;
@@ -148271,7 +148424,7 @@ function codexSessionsRoot() {
148271
148424
  const candidates = [];
148272
148425
  const ch = process.env["CODEX_HOME"];
148273
148426
  if (ch) candidates.push(path11.join(ch, "sessions"));
148274
- const home = process.env["HOME"] || os5.homedir();
148427
+ const home = process.env["HOME"] || os4.homedir();
148275
148428
  if (home) candidates.push(path11.join(home, ".codex", "sessions"));
148276
148429
  for (const c of candidates) {
148277
148430
  try {
@@ -148281,31 +148434,6 @@ function codexSessionsRoot() {
148281
148434
  }
148282
148435
  return "";
148283
148436
  }
148284
- function codexRolloutExists(sessionId, root = codexSessionsRoot()) {
148285
- if (!sessionId || !root) return false;
148286
- const suffix = `-${sessionId}.jsonl`;
148287
- let hit = false;
148288
- const walk = (d) => {
148289
- if (hit) return;
148290
- let ents;
148291
- try {
148292
- ents = fs11.readdirSync(d, { withFileTypes: true });
148293
- } catch {
148294
- return;
148295
- }
148296
- for (const ent of ents) {
148297
- if (hit) return;
148298
- const p2 = path11.join(d, ent.name);
148299
- if (ent.isDirectory()) walk(p2);
148300
- else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) {
148301
- hit = true;
148302
- return;
148303
- }
148304
- }
148305
- };
148306
- walk(root);
148307
- return hit;
148308
- }
148309
148437
  function listRecentRollouts(root, sinceMs) {
148310
148438
  const margin = sinceMs - 6e4;
148311
148439
  const found = [];
@@ -148547,17 +148675,18 @@ function normalizeCodexConsoleLine(line, state = createCodexNormalizeState()) {
148547
148675
  tool.output.push(line);
148548
148676
  return [];
148549
148677
  }
148550
- var import_node_child_process9, import_node_crypto14, fs11, os5, path11, readline2, CodexAdapter;
148678
+ var import_node_child_process9, import_node_crypto14, fs11, os4, path11, readline2, CodexAdapter;
148551
148679
  var init_codex = __esm({
148552
148680
  "../adapters/src/codex/index.ts"() {
148553
148681
  "use strict";
148554
148682
  import_node_child_process9 = require("node:child_process");
148555
148683
  import_node_crypto14 = require("node:crypto");
148556
148684
  fs11 = __toESM(require("node:fs"), 1);
148557
- os5 = __toESM(require("node:os"), 1);
148685
+ os4 = __toESM(require("node:os"), 1);
148558
148686
  path11 = __toESM(require("node:path"), 1);
148559
148687
  readline2 = __toESM(require("node:readline"), 1);
148560
148688
  init_sync_skills();
148689
+ init_resume_classify();
148561
148690
  init_session_paths();
148562
148691
  init_claude_code();
148563
148692
  CodexAdapter = class {
@@ -148606,9 +148735,7 @@ var init_codex = __esm({
148606
148735
  const task = job.bundle.files["TASK.md"];
148607
148736
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
148608
148737
  const prompt = job.systemPrompt ? ["# System Prompt", job.systemPrompt, "", "# Task", task].join("\n") : task;
148609
- const isResume = Boolean(
148610
- job.resumeRuntimeSession && job.runtimeSessionId && codexRolloutExists(job.runtimeSessionId)
148611
- );
148738
+ const isResume = Boolean(job.resumeRuntimeSession && job.runtimeSessionId);
148612
148739
  const args = [
148613
148740
  "exec",
148614
148741
  "-C",
@@ -148744,7 +148871,8 @@ var init_codex = __esm({
148744
148871
  const sawStreamError = streamErrorMessages.length > 0;
148745
148872
  const isError = !killReason && (typeof code === "number" && code !== 0 || sawStreamError);
148746
148873
  const errorMessage = isError ? [...streamErrorMessages, ...stdoutErrorLines, ...stderrTail].join("\n").trim().slice(-4e3) || void 0 : void 0;
148747
- const reason = killReason ? killReason : sawStreamError ? "error" : code === 0 ? "clean" : "error";
148874
+ const sessionNotFound = !killReason && isResume && isSessionNotFoundError([...streamErrorMessages, ...stdoutErrorLines, ...stderrTail].join("\n"));
148875
+ const reason = killReason ? killReason : sessionNotFound ? "session-not-found" : sawStreamError ? "error" : code === 0 ? "clean" : "error";
148748
148876
  finish({
148749
148877
  code: killReason ? null : code,
148750
148878
  reason,
@@ -153560,7 +153688,44 @@ var init_node_health = __esm({
153560
153688
  });
153561
153689
 
153562
153690
  // ../server/src/domains/nodes/routes.ts
153691
+ function daemonVersionLess(a, b2) {
153692
+ if (!a || !b2 || a === "\u2014" || b2 === "\u2014") return false;
153693
+ const parse2 = (v2) => v2.replace(/^v/, "").split(".").map((x2) => Number.parseInt(x2, 10));
153694
+ const pa = parse2(a);
153695
+ const pb = parse2(b2);
153696
+ if (pa.some(Number.isNaN) || pb.some(Number.isNaN)) return a < b2;
153697
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
153698
+ const ai = pa[i] ?? 0;
153699
+ const bi = pb[i] ?? 0;
153700
+ if (ai !== bi) return ai < bi;
153701
+ }
153702
+ return false;
153703
+ }
153563
153704
  function nodesDomain(deps) {
153705
+ async function resolveLatestDaemonVersion() {
153706
+ const envVer = (process.env["OASIS_LATEST_NODE_DAEMON_VERSION"] ?? "").trim();
153707
+ if (envVer) return { version: envVer, source: "env" };
153708
+ if (deps.fetchLatestNpmDaemonVersion) {
153709
+ try {
153710
+ const v2 = (await deps.fetchLatestNpmDaemonVersion())?.trim();
153711
+ if (v2) return { version: v2, source: "npm" };
153712
+ } catch {
153713
+ }
153714
+ }
153715
+ try {
153716
+ const here = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(__esm_import_meta_url));
153717
+ for (const rel of ["../../../../node-daemon/package.json", "../../../../../node-daemon/package.json"]) {
153718
+ try {
153719
+ const raw = (0, import_node_fs7.readFileSync)((0, import_node_path10.resolve)(here, rel), "utf8");
153720
+ const pkg = JSON.parse(raw);
153721
+ if (pkg.version) return { version: pkg.version, source: "monorepo" };
153722
+ } catch {
153723
+ }
153724
+ }
153725
+ } catch {
153726
+ }
153727
+ return { version: null, source: null };
153728
+ }
153564
153729
  let hubWired = false;
153565
153730
  function wireHub(hub) {
153566
153731
  if (hubWired) return;
@@ -153770,30 +153935,23 @@ function nodesDomain(deps) {
153770
153935
  return { status: 200, body: { ok: true } };
153771
153936
  });
153772
153937
  router.get("/api/nodes/expected-daemon-version", async () => {
153773
- const envVer = (process.env["OASIS_LATEST_NODE_DAEMON_VERSION"] ?? "").trim();
153774
- if (envVer) return { status: 200, body: { version: envVer, source: "env" } };
153775
- try {
153776
- const here = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(__esm_import_meta_url));
153777
- for (const rel of ["../../../../node-daemon/package.json", "../../../../../node-daemon/package.json"]) {
153778
- try {
153779
- const raw = (0, import_node_fs7.readFileSync)((0, import_node_path10.resolve)(here, rel), "utf8");
153780
- const pkg = JSON.parse(raw);
153781
- if (pkg.version) return { status: 200, body: { version: pkg.version, source: "monorepo" } };
153782
- } catch {
153783
- }
153784
- }
153785
- } catch {
153786
- }
153787
- return { status: 200, body: { version: null, source: null } };
153938
+ return { status: 200, body: await resolveLatestDaemonVersion() };
153788
153939
  });
153789
153940
  router.post("/api/nodes/:nodeId/update", async (req) => {
153790
153941
  const nodeId = req.params["nodeId"];
153791
153942
  if (!nodeId) return { status: 400, body: { error: "missing nodeId" } };
153792
153943
  const hub = deps.getHub();
153793
153944
  if (!hub) return { status: 503, body: { error: "node gateway \u672A\u5C31\u7EEA" } };
153945
+ const daemon = hub.connectedDaemons().find((d) => d.daemonId === nodeId);
153946
+ if (!daemon) return { status: 409, body: { error: `\u8282\u70B9 ${nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u65E0\u6CD5\u66F4\u65B0` } };
153947
+ const current = daemon.meta.daemonVersion;
153948
+ const { version: latest } = await resolveLatestDaemonVersion();
153949
+ if (latest && current && !daemonVersionLess(current, latest)) {
153950
+ return { status: 200, body: { ok: true, alreadyLatest: true, version: current } };
153951
+ }
153794
153952
  const sent = hub.dispatch(nodeId, { type: "update" });
153795
153953
  if (!sent) return { status: 409, body: { error: `\u8282\u70B9 ${nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u65E0\u6CD5\u66F4\u65B0` } };
153796
- return { status: 200, body: { ok: true } };
153954
+ return { status: 200, body: { ok: true, alreadyLatest: false } };
153797
153955
  });
153798
153956
  router.delete("/api/runtimes/:runtimeId", async (req) => {
153799
153957
  const runtimeId = req.params["runtimeId"];
@@ -164189,19 +164347,19 @@ var init_trajectory = __esm({
164189
164347
 
164190
164348
  // src/index.ts
164191
164349
  var fs26 = __toESM(require("node:fs"));
164192
- var os10 = __toESM(require("node:os"));
164350
+ var os9 = __toESM(require("node:os"));
164193
164351
  var path21 = __toESM(require("node:path"));
164194
164352
  var import_node_child_process15 = require("node:child_process");
164195
164353
 
164196
164354
  // ../cli/src/cli.ts
164197
164355
  var fs25 = __toESM(require("node:fs"), 1);
164198
- var os9 = __toESM(require("node:os"), 1);
164356
+ var os8 = __toESM(require("node:os"), 1);
164199
164357
  var path20 = __toESM(require("node:path"), 1);
164200
164358
  var import_node_crypto31 = require("node:crypto");
164201
164359
 
164202
164360
  // ../cli/src/serve.ts
164203
164361
  var fs23 = __toESM(require("node:fs"), 1);
164204
- var os6 = __toESM(require("node:os"), 1);
164362
+ var os5 = __toESM(require("node:os"), 1);
164205
164363
  var path18 = __toESM(require("node:path"), 1);
164206
164364
  var import_node_crypto28 = require("node:crypto");
164207
164365
  var import_node_url4 = require("node:url");
@@ -168088,6 +168246,62 @@ function refreshConnectorSkillsInBackground(dataDir, apply) {
168088
168246
  })();
168089
168247
  }
168090
168248
  var SESSION_TOKEN_GRACE_MS = 15 * 6e4;
168249
+ function makeResumeRetryProxy(first, spawnFallback) {
168250
+ const outCbs = [];
168251
+ const telCbs = [];
168252
+ const outBuf = [];
168253
+ const telBuf = [];
168254
+ let sawOutput = false;
168255
+ let active = first;
168256
+ const fanOut = (chunk) => {
168257
+ if (outCbs.length) for (const cb of outCbs) cb(chunk);
168258
+ else outBuf.push(chunk);
168259
+ };
168260
+ const fanTel = (e) => {
168261
+ if (telCbs.length) for (const cb of telCbs) cb(e);
168262
+ else telBuf.push(e);
168263
+ };
168264
+ const wire = (s2) => {
168265
+ s2.onOutput((c) => {
168266
+ sawOutput = true;
168267
+ fanOut(c);
168268
+ });
168269
+ s2.onTelemetry?.((e) => fanTel(e));
168270
+ };
168271
+ wire(first);
168272
+ const isSessionNotFound = (err) => {
168273
+ const chatExit = err instanceof Error ? err.chatExit : void 0;
168274
+ return chatExit?.reason === "session-not-found";
168275
+ };
168276
+ const done = first.done.catch(async (err) => {
168277
+ if (sawOutput || !isSessionNotFound(err)) throw err;
168278
+ const second = await spawnFallback();
168279
+ active = second;
168280
+ wire(second);
168281
+ await second.done;
168282
+ });
168283
+ return {
168284
+ get id() {
168285
+ return active.id;
168286
+ },
168287
+ get runId() {
168288
+ return active.runId;
168289
+ },
168290
+ onOutput(cb) {
168291
+ for (const c of outBuf.splice(0)) cb(c);
168292
+ outCbs.push(cb);
168293
+ },
168294
+ onTelemetry(cb) {
168295
+ for (const e of telBuf.splice(0)) cb(e);
168296
+ telCbs.push(cb);
168297
+ },
168298
+ done,
168299
+ kill: () => active.kill?.() ?? Promise.resolve(),
168300
+ get nativeSessionId() {
168301
+ return active.nativeSessionId;
168302
+ }
168303
+ };
168304
+ }
168091
168305
  function normalizeHttpBaseUrl(raw) {
168092
168306
  const url = new URL(raw);
168093
168307
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -168111,13 +168325,37 @@ function apiUrlFromGatewayUrl(raw) {
168111
168325
  return void 0;
168112
168326
  }
168113
168327
  }
168328
+ var NODE_DAEMON_PKG = "oasis_test";
168329
+ var NPM_LATEST_TTL_MS = 5 * 6e4;
168330
+ var npmLatestCache = null;
168331
+ async function fetchLatestNpmDaemonVersion() {
168332
+ if (npmLatestCache && Date.now() - npmLatestCache.atMs < NPM_LATEST_TTL_MS) return npmLatestCache.version;
168333
+ let version2 = null;
168334
+ try {
168335
+ const ctrl = new AbortController();
168336
+ const timer = setTimeout(() => ctrl.abort(), 3e3);
168337
+ try {
168338
+ const resp = await fetch(`https://registry.npmjs.org/${NODE_DAEMON_PKG}/latest`, { signal: ctrl.signal });
168339
+ if (resp.ok) {
168340
+ const body = await resp.json();
168341
+ version2 = typeof body.version === "string" ? body.version : null;
168342
+ }
168343
+ } finally {
168344
+ clearTimeout(timer);
168345
+ }
168346
+ } catch {
168347
+ version2 = null;
168348
+ }
168349
+ if (version2) npmLatestCache = { version: version2, atMs: Date.now() };
168350
+ return version2;
168351
+ }
168114
168352
  var oasisWrapperCache;
168115
168353
  function oasisWrapperScript() {
168116
168354
  if (oasisWrapperCache && fs23.existsSync(oasisWrapperCache)) return oasisWrapperCache;
168117
168355
  const repoRoot = (0, import_node_url4.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
168118
168356
  const tsx = path18.join(repoRoot, "node_modules/.bin/tsx");
168119
168357
  const main = path18.join(repoRoot, "packages/cli/src/main.ts");
168120
- const dir = fs23.mkdtempSync(path18.join(os6.tmpdir(), "oasis-cli-"));
168358
+ const dir = fs23.mkdtempSync(path18.join(os5.tmpdir(), "oasis-cli-"));
168121
168359
  const wrapper = path18.join(dir, "oasis");
168122
168360
  fs23.writeFileSync(wrapper, `#!/usr/bin/env bash
168123
168361
  exec ${JSON.stringify(tsx)} ${JSON.stringify(main)} "$@"
@@ -168328,7 +168566,7 @@ async function startServe(opts) {
168328
168566
  `[serve] \u8FD4\u5DE5\u6CE2\u7A33\u5B9A\u524D\u6CBF\uFF08rework-wavefront\uFF09\uFF1A${reworkWavefront ? "\u5DF2\u542F\u7528\uFF08\u9ED8\u8BA4\uFF09" : `\u26A0 \u5DF2\u88AB OASIS_REWORK_WAVEFRONT=${process.env.OASIS_REWORK_WAVEFRONT} \u663E\u5F0F\u5173\u95ED \u2014\u2014 \u56DE\u9000\u73B0\u72B6\u884C\u4E3A\uFF0C\u7B49\u7A33\u95E8/\u7B2C7\u9501/\u4E0A\u6E38\u5B88\u536B\u5168\u90E8\u4E0D\u751F\u6548`}`
168329
168567
  );
168330
168568
  const issuer = createTokenIssuer({ secretFile: path18.join(opts.dir, "session-token-secret") });
168331
- const operatorActor = `actor:human:${os6.userInfo().username}`;
168569
+ const operatorActor = `actor:human:${os5.userInfo().username}`;
168332
168570
  const operatorToken = issuer.issue(operatorActor);
168333
168571
  fs23.writeFileSync(path18.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
168334
168572
  let nodeTokens = createNodeTokenStore(path18.join(opts.dir, "node-tokens.json"));
@@ -168814,7 +169052,8 @@ async function startServe(opts) {
168814
169052
  gatewayUrl: opts.gatewayUrl ?? `ws://127.0.0.1:${opts.port ?? 7320}/node-gateway`,
168815
169053
  repoRemote: opts.nodeRepoRemote ?? "git@github.com:open-friday/oasis-core.git",
168816
169054
  nodeHealth,
168817
- activeRunCountOf
169055
+ activeRunCountOf,
169056
+ fetchLatestNpmDaemonVersion
168818
169057
  }),
168819
169058
  collabDomain({
168820
169059
  kernel,
@@ -168893,301 +169132,309 @@ async function startServe(opts) {
168893
169132
  discussSessions: new FileSideMap(path18.join(opts.dir, "discuss-sessions.json")),
168894
169133
  escalateDiscussRetain: new FileSideMap(path18.join(opts.dir, "escalate-discuss-retain.json")),
168895
169134
  // 切片 2b:代署 escalate 发起会话保留(落盘)
168896
- dispatchChat: async ({ actorId, message, sessionId, chatSessionId, attachments, workspace, companyId }) => {
168897
- const runCompanyId = companyId ?? defaultCompanyId;
168898
- const actorCtxForCompany = await actors.resolveCtx(runCompanyId);
168899
- const actorService = actorCtxForCompany.service;
168900
- const binding = await actorService.resolveBinding()(actorId);
168901
- if (!binding) {
168902
- throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u6570\u5B57\u5458\u5DE5 ${actorId} \u6CA1\u6709 active runtime \u7ED1\u5B9A`);
168903
- }
168904
- const node = hub?.connectedDaemons().find((d) => d.daemonId === binding.nodeId);
168905
- if (!node) {
168906
- throw new ApiError(409, "NODE_OFFLINE", `\u7ED1\u5B9A\u8282\u70B9 ${binding.nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF`);
168907
- }
168908
- if (!node.meta.adapters.includes(binding.runtimeKind)) {
168909
- throw new ApiError(409, "RUNTIME_UNAVAILABLE", `\u8282\u70B9 ${binding.nodeId} \u672A\u4E0A\u62A5\u8FD0\u884C\u65F6 ${binding.runtimeKind}`);
168910
- }
168911
- if (!chatRemoteAdapter) {
168912
- throw new ApiError(503, "NODE_GATEWAY_NOT_READY", "node-gateway \u5C1A\u672A\u5C31\u7EEA");
168913
- }
168914
- if (!localUrl) {
168915
- throw new ApiError(503, "SERVER_URL_NOT_READY", "server URL \u5C1A\u672A\u5C31\u7EEA");
168916
- }
168917
- const priorChatSession = chatSessionId ? await chatSessionStore?.getSession(chatSessionId).catch(() => null) : null;
168918
- const freshChatWorkdir = chatSessionId ? chatNodeChanged(
168919
- { nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
168920
- { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
168921
- ) : false;
168922
- const runtimeSessionId = sessionId ?? (0, import_node_crypto28.randomUUID)();
168923
- const resumeRuntimeSession = Boolean(sessionId);
168924
- const traceRunId = `chat-run:${(0, import_node_crypto28.randomUUID)()}`;
168925
- const artifactId = `artifact:chat:${(0, import_node_crypto28.randomUUID)()}`;
168926
- const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
168927
- let traceSeq = 0;
168928
- let lastProgressTouchMs = 0;
168929
- const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
168930
- let traceEnabled = true;
168931
- const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
168932
- let traceChain = traceStore.createRun({
168933
- id: traceRunId,
168934
- actorId,
168935
- runtimeKind: traceRuntimeKind(binding.runtimeKind),
168936
- runtimeInstanceId: `runtime:${binding.nodeId}:${binding.runtimeKind}`,
168937
- triggerKind: "api",
168938
- triggerRef: runtimeSessionId,
168939
- jobKey: runtimeSessionId,
168940
- artifactId,
168941
- action: "chat",
168942
- startedAt: traceStartedAt,
168943
- metadata: {
168944
- runtimeSessionId,
168945
- nodeId: binding.nodeId,
168946
- runtimeKind: binding.runtimeKind,
168947
- messagePreview: message.slice(0, 500),
168948
- ...analyzedRunId ? { analyzedRunId } : {}
168949
- }
168950
- }).then(async () => {
168951
- await traceStore.appendEvents(traceRunId, [
168952
- {
168953
- seq: ++traceSeq,
168954
- eventType: "run.started",
168955
- stream: "runtime",
168956
- message: `chat ${actorId}`,
168957
- startedAt: traceStartedAt
168958
- },
168959
- {
168960
- seq: ++traceSeq,
168961
- eventType: "input.message",
168962
- stream: "model",
168963
- actorId,
168964
- message: message.slice(0, 500),
168965
- payload: { text: message },
168966
- startedAt: traceStartedAt
168967
- }
168968
- ]);
168969
- }).catch((err) => {
168970
- traceEnabled = false;
168971
- console.warn(`[trace] chat run create failed: ${String(err)}`);
168972
- });
168973
- const enqueueTrace = (step) => {
168974
- if (!traceEnabled) return;
168975
- traceChain = traceChain.then(async () => {
168976
- if (traceEnabled) await step();
169135
+ dispatchChat: async (chatReq) => {
169136
+ const dispatchChatImpl = async ({ actorId, message, sessionId, chatSessionId, attachments, workspace, companyId }) => {
169137
+ const runCompanyId = companyId ?? defaultCompanyId;
169138
+ const actorCtxForCompany = await actors.resolveCtx(runCompanyId);
169139
+ const actorService = actorCtxForCompany.service;
169140
+ const binding = await actorService.resolveBinding()(actorId);
169141
+ if (!binding) {
169142
+ throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u6570\u5B57\u5458\u5DE5 ${actorId} \u6CA1\u6709 active runtime \u7ED1\u5B9A`);
169143
+ }
169144
+ const node = hub?.connectedDaemons().find((d) => d.daemonId === binding.nodeId);
169145
+ if (!node) {
169146
+ throw new ApiError(409, "NODE_OFFLINE", `\u7ED1\u5B9A\u8282\u70B9 ${binding.nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF`);
169147
+ }
169148
+ if (!node.meta.adapters.includes(binding.runtimeKind)) {
169149
+ throw new ApiError(409, "RUNTIME_UNAVAILABLE", `\u8282\u70B9 ${binding.nodeId} \u672A\u4E0A\u62A5\u8FD0\u884C\u65F6 ${binding.runtimeKind}`);
169150
+ }
169151
+ if (!chatRemoteAdapter) {
169152
+ throw new ApiError(503, "NODE_GATEWAY_NOT_READY", "node-gateway \u5C1A\u672A\u5C31\u7EEA");
169153
+ }
169154
+ if (!localUrl) {
169155
+ throw new ApiError(503, "SERVER_URL_NOT_READY", "server URL \u5C1A\u672A\u5C31\u7EEA");
169156
+ }
169157
+ const priorChatSession = chatSessionId ? await chatSessionStore?.getSession(chatSessionId).catch(() => null) : null;
169158
+ const freshChatWorkdir = chatSessionId ? chatNodeChanged(
169159
+ { nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
169160
+ { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
169161
+ ) : false;
169162
+ const runtimeSessionId = sessionId ?? (0, import_node_crypto28.randomUUID)();
169163
+ const resumeRuntimeSession = Boolean(sessionId);
169164
+ const traceRunId = `chat-run:${(0, import_node_crypto28.randomUUID)()}`;
169165
+ const artifactId = `artifact:chat:${(0, import_node_crypto28.randomUUID)()}`;
169166
+ const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
169167
+ let traceSeq = 0;
169168
+ let lastProgressTouchMs = 0;
169169
+ const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
169170
+ let traceEnabled = true;
169171
+ const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
169172
+ let traceChain = traceStore.createRun({
169173
+ id: traceRunId,
169174
+ actorId,
169175
+ runtimeKind: traceRuntimeKind(binding.runtimeKind),
169176
+ runtimeInstanceId: `runtime:${binding.nodeId}:${binding.runtimeKind}`,
169177
+ triggerKind: "api",
169178
+ triggerRef: runtimeSessionId,
169179
+ jobKey: runtimeSessionId,
169180
+ artifactId,
169181
+ action: "chat",
169182
+ startedAt: traceStartedAt,
169183
+ metadata: {
169184
+ runtimeSessionId,
169185
+ nodeId: binding.nodeId,
169186
+ runtimeKind: binding.runtimeKind,
169187
+ messagePreview: message.slice(0, 500),
169188
+ ...analyzedRunId ? { analyzedRunId } : {}
169189
+ }
169190
+ }).then(async () => {
169191
+ await traceStore.appendEvents(traceRunId, [
169192
+ {
169193
+ seq: ++traceSeq,
169194
+ eventType: "run.started",
169195
+ stream: "runtime",
169196
+ message: `chat ${actorId}`,
169197
+ startedAt: traceStartedAt
169198
+ },
169199
+ {
169200
+ seq: ++traceSeq,
169201
+ eventType: "input.message",
169202
+ stream: "model",
169203
+ actorId,
169204
+ message: message.slice(0, 500),
169205
+ payload: { text: message },
169206
+ startedAt: traceStartedAt
169207
+ }
169208
+ ]);
168977
169209
  }).catch((err) => {
168978
169210
  traceEnabled = false;
168979
- console.warn(`[trace] chat run ${traceRunId} failed: ${String(err)}`);
169211
+ console.warn(`[trace] chat run create failed: ${String(err)}`);
168980
169212
  });
168981
- };
168982
- const provision = await buildActorProvision(actorService, actorId);
168983
- const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
168984
- const chatResolvedModel = (() => {
168985
- if (actorCtx?.config?.model) return actorCtx.config.model;
168986
- return void 0;
168987
- })();
168988
- const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => c?.model).catch(() => void 0) ?? void 0;
168989
- const chatModelSource = chatResolvedModel ? "actor_config" : chatModel ? "runtime_default" : null;
168990
- const attachmentFiles = {};
168991
- const attachmentBinary = {};
168992
- const attachmentPaths = [];
168993
- const usedRel = /* @__PURE__ */ new Set();
168994
- for (const att of attachments ?? []) {
168995
- const safe = (att.name || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
168996
- let rel = `inputs/attachments/${safe}`;
168997
- let n = 1;
168998
- while (usedRel.has(rel)) rel = `inputs/attachments/${n++}-${safe}`;
168999
- usedRel.add(rel);
169000
- if (typeof att.text === "string") {
169001
- attachmentFiles[rel] = att.text;
169002
- attachmentPaths.push(rel);
169003
- } else if (att.blobRef) {
169004
- try {
169005
- const bytes = await assets.get(att.blobRef);
169006
- attachmentBinary[rel] = Buffer.from(bytes).toString("base64");
169213
+ const enqueueTrace = (step) => {
169214
+ if (!traceEnabled) return;
169215
+ traceChain = traceChain.then(async () => {
169216
+ if (traceEnabled) await step();
169217
+ }).catch((err) => {
169218
+ traceEnabled = false;
169219
+ console.warn(`[trace] chat run ${traceRunId} failed: ${String(err)}`);
169220
+ });
169221
+ };
169222
+ const provision = await buildActorProvision(actorService, actorId);
169223
+ const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
169224
+ const chatResolvedModel = (() => {
169225
+ if (actorCtx?.config?.model) return actorCtx.config.model;
169226
+ return void 0;
169227
+ })();
169228
+ const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => c?.model).catch(() => void 0) ?? void 0;
169229
+ const chatModelSource = chatResolvedModel ? "actor_config" : chatModel ? "runtime_default" : null;
169230
+ const attachmentFiles = {};
169231
+ const attachmentBinary = {};
169232
+ const attachmentPaths = [];
169233
+ const usedRel = /* @__PURE__ */ new Set();
169234
+ for (const att of attachments ?? []) {
169235
+ const safe = (att.name || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
169236
+ let rel = `inputs/attachments/${safe}`;
169237
+ let n = 1;
169238
+ while (usedRel.has(rel)) rel = `inputs/attachments/${n++}-${safe}`;
169239
+ usedRel.add(rel);
169240
+ if (typeof att.text === "string") {
169241
+ attachmentFiles[rel] = att.text;
169007
169242
  attachmentPaths.push(rel);
169008
- } catch (err) {
169009
- console.warn(`[chat] \u9644\u4EF6 blob ${att.blobRef} \u8BFB\u53D6\u5931\u8D25: ${String(err)}`);
169010
- }
169011
- }
169012
- }
169013
- const sessionLine = chatSessionId ? `chat_session_id: ${chatSessionId}` : `runtime_session_id: ${runtimeSessionId}`;
169014
- const taskHeader = workspace ? [
169015
- "# \u4E0E\u5DE5\u5355\u7BA1\u7406\u8005\u5BF9\u8BDD",
169016
- "",
169017
- sessionLine,
169018
- `actor_id: ${actorId}`,
169019
- `workspace: ${workspace}`,
169020
- "",
169021
- CONVERSATIONAL_MANAGER_BODY
169022
- ] : [
169023
- "# \u5373\u65F6\u8111\u66B4\u5BF9\u8BDD",
169024
- "",
169025
- sessionLine,
169026
- `actor_id: ${actorId}`,
169027
- "",
169028
- "\u8BF7\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u5199\u5165\u7CFB\u7EDF\uFF0C\u5426\u5219\u4E0D\u8981\u521B\u5EFA\u6216\u4FEE\u6539 artifact\u3002"
169029
- ];
169030
- const files = {
169031
- "TASK.md": [
169032
- ...taskHeader,
169033
- ...attachmentPaths.length ? ["", "## \u7528\u6237\u9644\u4EF6", "", "\u7528\u6237\u968F\u6D88\u606F\u9644\u5E26\u4E86\u4EE5\u4E0B\u6587\u4EF6\uFF08\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...attachmentPaths.map((p2) => `- ${p2}`)] : [],
169243
+ } else if (att.blobRef) {
169244
+ try {
169245
+ const bytes = await assets.get(att.blobRef);
169246
+ attachmentBinary[rel] = Buffer.from(bytes).toString("base64");
169247
+ attachmentPaths.push(rel);
169248
+ } catch (err) {
169249
+ console.warn(`[chat] \u9644\u4EF6 blob ${att.blobRef} \u8BFB\u53D6\u5931\u8D25: ${String(err)}`);
169250
+ }
169251
+ }
169252
+ }
169253
+ const sessionLine = chatSessionId ? `chat_session_id: ${chatSessionId}` : `runtime_session_id: ${runtimeSessionId}`;
169254
+ const taskHeader = workspace ? [
169255
+ "# \u4E0E\u5DE5\u5355\u7BA1\u7406\u8005\u5BF9\u8BDD",
169034
169256
  "",
169035
- "## \u7528\u6237\u6D88\u606F",
169257
+ sessionLine,
169258
+ `actor_id: ${actorId}`,
169259
+ `workspace: ${workspace}`,
169036
169260
  "",
169037
- message
169038
- ].join("\n")
169039
- };
169040
- Object.assign(files, attachmentFiles);
169041
- if (actorCtx?.config?.prompt) {
169042
- Object.assign(files, splitIdentityFiles3(actorCtx.config.prompt));
169043
- }
169044
- if (!SKILL_CACHE_RUNTIMES.has(binding.runtimeKind)) {
169045
- Object.assign(files, materializeBuiltins(builtinSkills, binding.runtimeKind));
169046
- Object.assign(files, await materializeSkillFiles({
169047
- service: actorService,
169048
- actorId,
169049
- runtimeKind: binding.runtimeKind
169050
- }).catch((err) => {
169051
- console.warn(`[chat] skill materialize failed for ${actorId}: ${String(err)}`);
169052
- return {};
169053
- }));
169054
- }
169055
- const limits = { wallClockMs: SESSION_WALL_CLOCK_MS.chat };
169056
- const handle = await chatRemoteAdapter.spawn({
169057
- actor: actorId,
169058
- actorToken: issueSessionToken(actorId, { artifactId, action: "chat", limits, binding, sessionId: runtimeSessionId, companyId: runCompanyId, ...chatSessionId !== void 0 ? { chatSessionId } : {} }),
169059
- artifactId,
169060
- bundle: { files, ...Object.keys(attachmentBinary).length ? { binaryFiles: attachmentBinary } : {} },
169061
- ...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
169062
- ...chatModel ? { model: chatModel } : {},
169063
- // ADR-0053 D1:chat 的**会话线 id 是 chatSessionId**(Oasis 命名空间、跨轮不变),不是 rtId
169064
- // (rtId 跨轮会变:换节点时服务端主动清掉它、codex/ACP 还会自己另分配)。
169065
- //
169066
- // 没有 chatSessionId → **不给 workdirKey** → 落 one-shot,这是契约的直接应用而不是折中:
169067
- // 服务端对这种请求也不落库消息(persistTarget 要求 chatSessionId),没有会话线、没有历史,
169068
- // 本就没什么可续的。产品路径跑不到这条分支(前端三个调用方都保证先建会话再传),只兜住
169069
- // 裸 API 调用。
169070
- ...chatSessionId ? { workdirKey: `chat:${chatSessionId}` } : {},
169071
- // 与上一条成对:稳定目录一上,A→B→A 回到 node1 就会撞见同名目录里 A 自己 N 轮前的陈旧
169072
- // 文件(而注入的历史正告诉它「B 改了 X」)。切根前没这问题纯属侥幸——那时换节点会清掉
169073
- // rtId、rtId 又是目录名,"新目录"是副产品;稳定目录一上,侥幸就没了。
169074
- //
169075
- // **在这儿算,不放进 ChatDispatchRequest**:本函数是唯一同时握着 binding 与 chatSessionId
169076
- // 的地方,四个调用方(/api/chat、discuss、飞书入站、草案评审通知)全覆盖且谁都不用记得
169077
- // 传。放进契约就等于要求每个调用方自己算,而其中三个压根不碰 binding。
169078
- ...freshChatWorkdir ? { freshWorkdir: true } : {},
169079
- runtimeSessionId,
169080
- resumeRuntimeSession,
169081
- server: { url: serverUrlForBinding(binding) },
169082
- binding,
169083
- limits,
169084
- // OASIS_STAGE=1:chat agent 的改图命令(edit/link/spawn/cancel)经 /api/stage → 草案路由
169085
- // (建单/改图暂存统一,见 docs/proposals/draft-edit-unification.md)。
169086
- // OASIS_WORKSPACE:给定工单(协调者对话)时绑上——staged 编辑的 type:title 在本工单范围解析,
169087
- // 产出的草案 plan.workspace = 本工单,前端「待确认变更」按工单过滤才抓得到。普通脑暴不传,保持不绑。
169088
- env: {
169089
- ...provision.env,
169090
- OASIS_STAGE: "1",
169091
- ...chatSessionId ? { OASIS_CHAT_SESSION_ID: chatSessionId } : {},
169092
- ...workspace ? { OASIS_WORKSPACE: workspace } : {}
169093
- },
169094
- wrapperPaths: provision.wrapperPaths,
169095
- ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {}
169096
- });
169097
- const chatJobKey = `chat::${artifactId}`;
169098
- chatLiveSessions.set(chatJobKey, { artifactId, sessionId: traceRunId, dispatchId: handle.id, kill: () => handle.kill() });
169099
- enqueueTrace(async () => {
169100
- const run = await traceStore.getRun(traceRunId);
169101
- if (!run) return;
169102
- const base = run.metadata && typeof run.metadata === "object" && !Array.isArray(run.metadata) ? run.metadata : {};
169103
- await traceStore.updateRun(traceRunId, {
169104
- metadata: { ...base, dispatchId: handle.id },
169105
- // 恢复路径拿不到 dispatchChat 闭包里的 fallback 解析结果,开跑时先写进账本;退出帧若上报
169106
- // 更精确 model 仍会覆盖为 runtime 来源。
169107
- ...chatModel ? { effectiveModel: chatModel, modelSource: chatModelSource } : {}
169261
+ CONVERSATIONAL_MANAGER_BODY
169262
+ ] : [
169263
+ "# \u5373\u65F6\u8111\u66B4\u5BF9\u8BDD",
169264
+ "",
169265
+ sessionLine,
169266
+ `actor_id: ${actorId}`,
169267
+ "",
169268
+ "\u8BF7\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u5199\u5165\u7CFB\u7EDF\uFF0C\u5426\u5219\u4E0D\u8981\u521B\u5EFA\u6216\u4FEE\u6539 artifact\u3002"
169269
+ ];
169270
+ const files = {
169271
+ "TASK.md": [
169272
+ ...taskHeader,
169273
+ ...attachmentPaths.length ? ["", "## \u7528\u6237\u9644\u4EF6", "", "\u7528\u6237\u968F\u6D88\u606F\u9644\u5E26\u4E86\u4EE5\u4E0B\u6587\u4EF6\uFF08\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...attachmentPaths.map((p2) => `- ${p2}`)] : [],
169274
+ "",
169275
+ "## \u7528\u6237\u6D88\u606F",
169276
+ "",
169277
+ message
169278
+ ].join("\n")
169279
+ };
169280
+ Object.assign(files, attachmentFiles);
169281
+ if (actorCtx?.config?.prompt) {
169282
+ Object.assign(files, splitIdentityFiles3(actorCtx.config.prompt));
169283
+ }
169284
+ if (!SKILL_CACHE_RUNTIMES.has(binding.runtimeKind)) {
169285
+ Object.assign(files, materializeBuiltins(builtinSkills, binding.runtimeKind));
169286
+ Object.assign(files, await materializeSkillFiles({
169287
+ service: actorService,
169288
+ actorId,
169289
+ runtimeKind: binding.runtimeKind
169290
+ }).catch((err) => {
169291
+ console.warn(`[chat] skill materialize failed for ${actorId}: ${String(err)}`);
169292
+ return {};
169293
+ }));
169294
+ }
169295
+ const limits = { wallClockMs: SESSION_WALL_CLOCK_MS.chat };
169296
+ const handle = await chatRemoteAdapter.spawn({
169297
+ actor: actorId,
169298
+ actorToken: issueSessionToken(actorId, { artifactId, action: "chat", limits, binding, sessionId: runtimeSessionId, companyId: runCompanyId, ...chatSessionId !== void 0 ? { chatSessionId } : {} }),
169299
+ artifactId,
169300
+ bundle: { files, ...Object.keys(attachmentBinary).length ? { binaryFiles: attachmentBinary } : {} },
169301
+ ...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
169302
+ ...chatModel ? { model: chatModel } : {},
169303
+ // ADR-0053 D1:chat 的**会话线 id 是 chatSessionId**(Oasis 命名空间、跨轮不变),不是 rtId
169304
+ // (rtId 跨轮会变:换节点时服务端主动清掉它、codex/ACP 还会自己另分配)。
169305
+ //
169306
+ // 没有 chatSessionId → **不给 workdirKey** → 落 one-shot,这是契约的直接应用而不是折中:
169307
+ // 服务端对这种请求也不落库消息(persistTarget 要求 chatSessionId),没有会话线、没有历史,
169308
+ // 本就没什么可续的。产品路径跑不到这条分支(前端三个调用方都保证先建会话再传),只兜住
169309
+ // API 调用。
169310
+ ...chatSessionId ? { workdirKey: `chat:${chatSessionId}` } : {},
169311
+ // 与上一条成对:稳定目录一上,A→B→A 回到 node1 就会撞见同名目录里 A 自己 N 轮前的陈旧
169312
+ // 文件(而注入的历史正告诉它「B 改了 X」)。切根前没这问题纯属侥幸——那时换节点会清掉
169313
+ // rtId、rtId 又是目录名,"新目录"是副产品;稳定目录一上,侥幸就没了。
169314
+ //
169315
+ // **在这儿算,不放进 ChatDispatchRequest**:本函数是唯一同时握着 binding chatSessionId
169316
+ // 的地方,四个调用方(/api/chat、discuss、飞书入站、草案评审通知)全覆盖且谁都不用记得
169317
+ // 传。放进契约就等于要求每个调用方自己算,而其中三个压根不碰 binding。
169318
+ ...freshChatWorkdir ? { freshWorkdir: true } : {},
169319
+ runtimeSessionId,
169320
+ resumeRuntimeSession,
169321
+ server: { url: serverUrlForBinding(binding) },
169322
+ binding,
169323
+ limits,
169324
+ // OASIS_STAGE=1:chat agent 的改图命令(edit/link/spawn/cancel)经 /api/stage → 草案路由
169325
+ // (建单/改图暂存统一,见 docs/proposals/draft-edit-unification.md)。
169326
+ // OASIS_WORKSPACE:给定工单(协调者对话)时绑上——staged 编辑的 type:title 在本工单范围解析,
169327
+ // 产出的草案 plan.workspace = 本工单,前端「待确认变更」按工单过滤才抓得到。普通脑暴不传,保持不绑。
169328
+ env: {
169329
+ ...provision.env,
169330
+ OASIS_STAGE: "1",
169331
+ ...chatSessionId ? { OASIS_CHAT_SESSION_ID: chatSessionId } : {},
169332
+ ...workspace ? { OASIS_WORKSPACE: workspace } : {}
169333
+ },
169334
+ wrapperPaths: provision.wrapperPaths,
169335
+ ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {}
169108
169336
  });
169109
- });
169110
- const buffered = [];
169111
- const bufferedTelemetry = [];
169112
- const outputCbs = [];
169113
- const telemetryCbs = [];
169114
- handle.onOutput?.((chunk) => {
169115
- if (outputCbs.length === 0) buffered.push(chunk);
169116
- for (const cb of outputCbs) cb(chunk);
169117
- });
169118
- handle.onTelemetry?.((event) => {
169119
- if (telemetryCbs.length === 0) bufferedTelemetry.push(event);
169120
- for (const cb of telemetryCbs) cb(event);
169121
- if (event.kind === "system") {
169122
- const ms = Date.parse(event.ts);
169123
- if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
169124
- lastProgressTouchMs = ms;
169125
- enqueueTrace(() => traceStore.updateRun(traceRunId, { lastProgressAt: event.ts }).then(() => void 0));
169337
+ const chatJobKey = `chat::${artifactId}`;
169338
+ chatLiveSessions.set(chatJobKey, { artifactId, sessionId: traceRunId, dispatchId: handle.id, kill: () => handle.kill() });
169339
+ enqueueTrace(async () => {
169340
+ const run = await traceStore.getRun(traceRunId);
169341
+ if (!run) return;
169342
+ const base = run.metadata && typeof run.metadata === "object" && !Array.isArray(run.metadata) ? run.metadata : {};
169343
+ await traceStore.updateRun(traceRunId, {
169344
+ metadata: { ...base, dispatchId: handle.id },
169345
+ // 恢复路径拿不到 dispatchChat 闭包里的 fallback 解析结果,开跑时先写进账本;退出帧若上报
169346
+ // 更精确 model 仍会覆盖为 runtime 来源。
169347
+ ...chatModel ? { effectiveModel: chatModel, modelSource: chatModelSource } : {}
169348
+ });
169349
+ });
169350
+ const buffered = [];
169351
+ const bufferedTelemetry = [];
169352
+ const outputCbs = [];
169353
+ const telemetryCbs = [];
169354
+ handle.onOutput?.((chunk) => {
169355
+ if (outputCbs.length === 0) buffered.push(chunk);
169356
+ for (const cb of outputCbs) cb(chunk);
169357
+ });
169358
+ handle.onTelemetry?.((event) => {
169359
+ if (telemetryCbs.length === 0) bufferedTelemetry.push(event);
169360
+ for (const cb of telemetryCbs) cb(event);
169361
+ if (event.kind === "system") {
169362
+ const ms = Date.parse(event.ts);
169363
+ if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
169364
+ lastProgressTouchMs = ms;
169365
+ enqueueTrace(() => traceStore.updateRun(traceRunId, { lastProgressAt: event.ts }).then(() => void 0));
169366
+ }
169367
+ return;
169126
169368
  }
169127
- return;
169128
- }
169129
- enqueueTrace(() => traceStore.appendEvents(traceRunId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
169130
- });
169131
- let capturedNativeSessionId;
169132
- const done = new Promise((resolve5, reject) => {
169133
- handle.onExit((info) => {
169134
- chatLiveSessions.delete(chatJobKey);
169135
- void provision.cleanup();
169136
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
169137
- const durationMs = Date.parse(endedAt) - Date.parse(traceStartedAt);
169138
- if (info.runtimeSessionId) capturedNativeSessionId = info.runtimeSessionId;
169139
- enqueueTrace(async () => {
169140
- await traceStore.appendEvents(traceRunId, [{
169141
- seq: ++traceSeq,
169142
- eventType: "run.finished",
169143
- stream: "runtime",
169144
- message: `exit code=${info.code ?? "killed"}`,
169145
- startedAt: endedAt
169146
- }]);
169147
- const usage = info.usage ?? null;
169148
- const effectiveModel = info.model ?? chatModel ?? null;
169149
- const modelSource = info.model ? "runtime" : chatModelSource;
169150
- await traceStore.updateRun(traceRunId, {
169151
- status: info.reason === "timeout" ? "timeout" : info.reason === "cancelled" ? "cancelled" : info.code === 0 ? "succeeded" : "failed",
169152
- endedAt,
169153
- durationMs,
169154
- exitCode: info.code,
169155
- exitReason: info.reason ?? null,
169156
- transcriptRef: info.transcriptRef ?? null,
169157
- usage,
169158
- effectiveModel,
169159
- modelSource
169369
+ enqueueTrace(() => traceStore.appendEvents(traceRunId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
169370
+ });
169371
+ let capturedNativeSessionId;
169372
+ const done = new Promise((resolve5, reject) => {
169373
+ handle.onExit((info) => {
169374
+ chatLiveSessions.delete(chatJobKey);
169375
+ void provision.cleanup();
169376
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
169377
+ const durationMs = Date.parse(endedAt) - Date.parse(traceStartedAt);
169378
+ if (info.runtimeSessionId) capturedNativeSessionId = info.runtimeSessionId;
169379
+ enqueueTrace(async () => {
169380
+ await traceStore.appendEvents(traceRunId, [{
169381
+ seq: ++traceSeq,
169382
+ eventType: "run.finished",
169383
+ stream: "runtime",
169384
+ message: `exit code=${info.code ?? "killed"}`,
169385
+ startedAt: endedAt
169386
+ }]);
169387
+ const usage = info.usage ?? null;
169388
+ const effectiveModel = info.model ?? chatModel ?? null;
169389
+ const modelSource = info.model ? "runtime" : chatModelSource;
169390
+ await traceStore.updateRun(traceRunId, {
169391
+ status: info.reason === "timeout" ? "timeout" : info.reason === "cancelled" ? "cancelled" : info.code === 0 ? "succeeded" : "failed",
169392
+ endedAt,
169393
+ durationMs,
169394
+ exitCode: info.code,
169395
+ exitReason: info.reason ?? null,
169396
+ transcriptRef: info.transcriptRef ?? null,
169397
+ usage,
169398
+ effectiveModel,
169399
+ modelSource
169400
+ });
169401
+ if (usage && traceStore.updateUsageAggregates) {
169402
+ const run = await traceStore.getRun(traceRunId);
169403
+ if (run) void traceStore.updateUsageAggregates(run).catch(() => {
169404
+ });
169405
+ }
169160
169406
  });
169161
- if (usage && traceStore.updateUsageAggregates) {
169162
- const run = await traceStore.getRun(traceRunId);
169163
- if (run) void traceStore.updateUsageAggregates(run).catch(() => {
169164
- });
169407
+ const failed = info.code !== 0 || info.reason !== void 0 && info.reason !== "clean";
169408
+ if (!failed) resolve5();
169409
+ else {
169410
+ reject(buildChatExitError(info));
169165
169411
  }
169166
169412
  });
169167
- const failed = info.code !== 0 || info.reason !== void 0 && info.reason !== "clean";
169168
- if (!failed) resolve5();
169169
- else {
169170
- reject(buildChatExitError(info));
169171
- }
169172
169413
  });
169173
- });
169174
- return {
169175
- id: runtimeSessionId,
169176
- runId: traceRunId,
169177
- onOutput(cb) {
169178
- for (const chunk of buffered.splice(0)) cb(chunk);
169179
- outputCbs.push(cb);
169180
- },
169181
- onTelemetry(cb) {
169182
- for (const event of bufferedTelemetry.splice(0)) cb(event);
169183
- telemetryCbs.push(cb);
169184
- },
169185
- done,
169186
- kill: () => handle.kill(),
169187
- get nativeSessionId() {
169188
- return capturedNativeSessionId;
169189
- }
169414
+ return {
169415
+ id: runtimeSessionId,
169416
+ runId: traceRunId,
169417
+ onOutput(cb) {
169418
+ for (const chunk of buffered.splice(0)) cb(chunk);
169419
+ outputCbs.push(cb);
169420
+ },
169421
+ onTelemetry(cb) {
169422
+ for (const event of bufferedTelemetry.splice(0)) cb(event);
169423
+ telemetryCbs.push(cb);
169424
+ },
169425
+ done,
169426
+ kill: () => handle.kill(),
169427
+ get nativeSessionId() {
169428
+ return capturedNativeSessionId;
169429
+ }
169430
+ };
169190
169431
  };
169432
+ const first = await dispatchChatImpl(chatReq);
169433
+ if (!chatReq.fallbackMessage || !chatReq.sessionId) return first;
169434
+ return makeResumeRetryProxy(
169435
+ first,
169436
+ () => dispatchChatImpl({ ...chatReq, sessionId: void 0, fallbackMessage: void 0, message: chatReq.fallbackMessage })
169437
+ );
169191
169438
  },
169192
169439
  trajectoryFeed: {
169193
169440
  subscribe(sessionId, listener) {
@@ -170507,9 +170754,15 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
170507
170754
  design run <artifactId> [--brief <t>] [--reason <t>] [--od-url <url>] [--title <t>] [--agent <id>]
170508
170755
  # design \u4EA7\u7269\uFF1A\u5728\u901A\u7528 runtime \u4E0A\u9A71\u52A8 open-design \u753B\u677F\u5F15\u64CE\u4EA7\u754C\u9762\u7A3F\u5E76\u4EA4\u7A3F\uFF08\u63D0\u6848 design-node-runtime-to-cli-tool\uFF09\u3002
170509
170756
  # --brief \u7F3A\u7701\u8BFB\u5F53\u524D\u76EE\u5F55 DESIGN_BRIEF.md\uFF08\u5176\u6B21 TASK.md\uFF09\uFF1B\u4E0A\u4E0B\u6587\u53D6\u5F53\u524D\u76EE\u5F55 inputs/\uFF1Bod \u5730\u5740\u7F3A\u7701 $OASIS_OPEN_DESIGN_URL \u6216\u672C\u673A 7457\u3002
170757
+ design chat <artifactId> --message <t> [--turn first|continue] [--async] [--system <t>]
170758
+ # \u591A\u8F6E\u5BF9\u8BDD\u9A71\u52A8\u753B\u677F\uFF08\u63D0\u6848 design-conversational-driving\uFF09\uFF1A\u4E0D\u81EA\u52A8\u4EA4\u7A3F\uFF0C\u8BFB\u4EA7\u7269\u540E\u81EA\u884C propose/gap\u3002
170759
+ # --async \u8D77\u5B8C\u5373\u8FD4\uFF08\u5F15\u64CE\u5728\u670D\u52A1\u7AEF\u8DD1 13\u201344min\uFF0C\u4E0E\u672C\u8FDB\u7A0B\u65E0\u5173\uFF09\uFF1B\u8FDB\u5EA6/\u53D6\u8D27\u7528 design status\u3002
170760
+ design status <artifactId> [--pull]
170761
+ # \u67E5\u753B\u677F\u73B0\u72B6\uFF08\u6062\u590D\u4F18\u5148\uFF09\uFF1A\u88AB\u6D3E\u53D1\u5148\u8DD1\u5B83\u2014\u2014\u5F15\u64CE\u5728\u8DD1\u5C31\u522B\u8D77\u65B0\u8F6E\uFF1B\u677F\u4E0A\u6709\u8D27 --pull \u62C9\u672C\u5730\u8BFB\uFF0C\u8BFB\u5B8C propose --from-dir\u3002
170510
170762
 
170511
170763
  \u56FE\u673A\u5236\uFF08\xA76/\xA77\uFF09
170512
170764
  annotate <artifactId> --body <text> [--from <\u4E0B\u6E38> --blocks true]
170765
+ record-action <artifactId> --name <\u52A8\u4F5C\u540D> [--pass true] [--result <json>] [--params <json>] # \xA79.2 grounding \u7559\u75D5\uFF1A\u4E3A action_vs_oplog \u8BB0\u4E00\u6761\u901A\u8FC7\u7684 tool_call\uFF08\u771F\u8DD1\u8FC7\u5065\u5EB7\u63A2\u9488\u540E record-action --name health_probe --pass true\uFF09\uFF1B\u58F0\u660E\u4E86 requiredActions \u7684\u7C7B\u578B\u6CA1\u6709\u5B83 conclude \u4F1A\u88AB\u673A\u68B0\u62E6
170513
170766
  resolve <annotationId> [--as resolved|wontfix|acknowledged]
170514
170767
  gap <artifactId> --desc <text>
170515
170768
  add-revision <artifactId> --desc <\u8981\u5B83\u5B8C\u6210\u4EC0\u4E48> [--role <\u8C01\u6765\u505A\uFF0C\u9ED8\u8BA4owner\u81EA\u5DF1>] # \xA79 \u592A\u5927\u5C31\u6D3E\u4E00\u6761\u5B50revision\u51FA\u53BB\u5E76\u884C\uFF0C\u6D3E\u5B8C\u53EF\u6536\u5DE5\u7B49\u7ED3\u679C
@@ -170568,7 +170821,7 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
170568
170821
  function parseArgs(argv) {
170569
170822
  const positional = [];
170570
170823
  const flags = /* @__PURE__ */ new Map();
170571
- const booleanFlags = /* @__PURE__ */ new Set(["checkpoint", "continue"]);
170824
+ const booleanFlags = /* @__PURE__ */ new Set(["checkpoint", "continue", "async", "pull"]);
170572
170825
  for (let i = 0; i < argv.length; i++) {
170573
170826
  const a = argv[i];
170574
170827
  if (a.startsWith("--")) {
@@ -170673,7 +170926,7 @@ async function runCli(argv, println = console.log) {
170673
170926
  const res = await fetch(`${httpBase2}/api/nodes/exchange`, {
170674
170927
  method: "POST",
170675
170928
  headers: { "content-type": "application/json" },
170676
- body: JSON.stringify({ enrollToken, nodeId, hostname: os9.hostname() })
170929
+ body: JSON.stringify({ enrollToken, nodeId, hostname: os8.hostname() })
170677
170930
  });
170678
170931
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
170679
170932
  const body = await res.json();
@@ -170706,7 +170959,7 @@ async function runCli(argv, println = console.log) {
170706
170959
  const binPath = path20.join(prefix, "bin", "oasis_test");
170707
170960
  const svcArgs = `node --server-ws ${serverUrl} --dir ${dir}${nodeName ? ` --name ${nodeName}` : ""}`;
170708
170961
  if (process.platform === "linux") {
170709
- const svcFile = path20.join(os9.homedir(), ".config/systemd/user/oasis-node.service");
170962
+ const svcFile = path20.join(os8.homedir(), ".config/systemd/user/oasis-node.service");
170710
170963
  fs25.mkdirSync(path20.dirname(svcFile), { recursive: true });
170711
170964
  if (spawnSync("systemctl", ["--user", "is-active", "--quiet", "oasis-node"], {}).status === 0)
170712
170965
  spawnSync("systemctl", ["--user", "stop", "oasis-node"], { stdio: "inherit" });
@@ -170719,7 +170972,7 @@ async function runCli(argv, println = console.log) {
170719
170972
  `ExecStart=${binPath} ${svcArgs}`,
170720
170973
  // Carry the install-time PATH so the detached daemon detects CLIs in user dirs
170721
170974
  // (~/.npm-global/bin, ~/.local/bin) — systemd --user otherwise gives a minimal PATH.
170722
- `Environment=PATH=${process.env["PATH"] ?? ""}:${path20.join(os9.homedir(), ".npm-global/bin")}:${path20.join(os9.homedir(), ".local/bin")}:/usr/local/bin`,
170975
+ `Environment=PATH=${process.env["PATH"] ?? ""}:${path20.join(os8.homedir(), ".npm-global/bin")}:${path20.join(os8.homedir(), ".local/bin")}:/usr/local/bin`,
170723
170976
  "Restart=on-failure",
170724
170977
  "RestartSec=5",
170725
170978
  "StandardOutput=journal",
@@ -170731,23 +170984,23 @@ async function runCli(argv, println = console.log) {
170731
170984
  ].join("\n"));
170732
170985
  spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
170733
170986
  spawnSync("systemctl", ["--user", "enable", "--now", "oasis-node"], { stdio: "inherit" });
170734
- spawnSync("loginctl", ["enable-linger", os9.userInfo().username], { stdio: "ignore" });
170987
+ spawnSync("loginctl", ["enable-linger", os8.userInfo().username], { stdio: "ignore" });
170735
170988
  println("\u2713 Oasis node service started (systemd --user oasis-node)");
170736
170989
  println(" Status : systemctl --user status oasis-node");
170737
170990
  println(" Logs : journalctl --user -u oasis-node -f");
170738
170991
  println(" Stop : systemctl --user stop oasis-node");
170739
170992
  } else if (process.platform === "darwin") {
170740
- const plist = path20.join(os9.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
170993
+ const plist = path20.join(os8.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
170741
170994
  fs25.mkdirSync(path20.dirname(plist), { recursive: true });
170742
170995
  spawnSync("launchctl", ["unload", plist], { stdio: "ignore" });
170743
170996
  const argXml = [binPath, "node", "--server-ws", serverUrl, "--dir", dir, ...nodeName ? ["--name", nodeName] : []].map((a) => `<string>${a}</string>`).join("");
170744
- fs25.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os9.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os9.homedir()}/.oasis-node.err</string></dict></plist>`);
170997
+ fs25.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os8.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os8.homedir()}/.oasis-node.err</string></dict></plist>`);
170745
170998
  spawnSync("launchctl", ["load", plist], { stdio: "inherit" });
170746
170999
  println("\u2713 Oasis node service started (macOS LaunchAgent com.oasis.node)");
170747
- println(` Logs : tail -f ${os9.homedir()}/.oasis-node.log`);
171000
+ println(` Logs : tail -f ${os8.homedir()}/.oasis-node.log`);
170748
171001
  println(` Stop : launchctl unload ${plist}`);
170749
171002
  } else {
170750
- spawnSync("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path20.join(os9.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
171003
+ spawnSync("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path20.join(os8.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
170751
171004
  println(`\u2713 Oasis node started in background (nohup). Logs: ~/.oasis-node.log`);
170752
171005
  println(" Note: no automatic restart on reboot in this environment.");
170753
171006
  }
@@ -170818,8 +171071,8 @@ async function runCli(argv, println = console.log) {
170818
171071
  if (command === "serve" && positional[0] === "install") {
170819
171072
  const absDir = path20.resolve(dir);
170820
171073
  const name = `oasis-serve-${path20.basename(absDir)}`;
170821
- const unitDir = flags.get("unit-dir") ?? path20.join(os9.homedir(), ".config", "systemd", "user");
170822
- const bin = flags.get("bin") ?? path20.join(os9.homedir(), ".local", "bin", "oasis");
171074
+ const unitDir = flags.get("unit-dir") ?? path20.join(os8.homedir(), ".config", "systemd", "user");
171075
+ const bin = flags.get("bin") ?? path20.join(os8.homedir(), ".local", "bin", "oasis");
170823
171076
  const port = flags.get("port") ?? "7320";
170824
171077
  const extra = (flags.get("dispatch") === "true" ? ` --dispatch true` : "") + (flags.has("model") ? ` --model ${flags.get("model")}` : "");
170825
171078
  const unit = [
@@ -171404,7 +171657,81 @@ ${res.warning}`);
171404
171657
  }
171405
171658
  case "design": {
171406
171659
  const sub = positional[0];
171407
- if (sub !== "run") throw new Error("\u7528\u6CD5\uFF1Aoasis design run <artifactId> [--brief <t>] [--reason <t>] [--od-url <url>] [--title <t>] [--agent <id>]");
171660
+ const LEGACY_DESIGN_SYSTEM_PROMPT = "\u4F60\u662F\u8BBE\u8BA1\u5F15\u64CE\u4E2D\u7684\u754C\u9762\u8BBE\u8BA1\u5E08\u3002\u4EFB\u52A1\u4E66\u89C1\u672C\u6761\u6D88\u606F\uFF1B\u4E0A\u6E38\u8F93\u5165\u7B49\u4E0A\u4E0B\u6587\u6587\u4EF6\u5728\u9879\u76EE task-context/ \u76EE\u5F55\u3002\u6309\u4EFB\u52A1\u4E66\u7684\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u534F\u8BAE\u5DE5\u4F5C\uFF1A\u4EA7\u51FA\u81EA\u5305\u542B HTML \u5165\u53E3\u7A3F\u4E0E acceptance.md\uFF0C\u5B8C\u6210\u5373\u7ED3\u675F\u56DE\u590D\uFF0C\u4EA4\u7A3F\u7531\u7CFB\u7EDF\u4EE3\u529E\u3002";
171661
+ if (sub === "chat") {
171662
+ const artifactId2 = needPos(positional, 1, "oasis design chat <artifactId> --message <t>");
171663
+ const message2 = flags.get("message") ?? (["DESIGN_BRIEF.md", "TASK.md"].map((f2) => fs25.existsSync(f2) ? fs25.readFileSync(f2, "utf8") : "").find((s2) => s2.trim()) ?? "");
171664
+ if (!message2.trim()) throw new Error("\u65E0\u5BF9\u8BDD\u5185\u5BB9\uFF1A\u7ED9 --message <text>\uFF0C\u6216\u5728\u5F53\u524D\u76EE\u5F55\u653E DESIGN_BRIEF.md / TASK.md");
171665
+ const odBase2 = (flags.get("od-url") ?? process.env["OASIS_OPEN_DESIGN_URL"] ?? "http://127.0.0.1:7457").replace(/\/$/, "");
171666
+ const isContinue = flags.get("turn") === "continue";
171667
+ const contextFiles2 = {};
171668
+ if (!isContinue) {
171669
+ contextFiles2["DESIGN_BRIEF.md"] = message2;
171670
+ if (fs25.existsSync("inputs") && fs25.statSync("inputs").isDirectory()) {
171671
+ for (const f2 of readDirFiles("inputs")) {
171672
+ contextFiles2[`inputs/${f2.path}`] = Buffer.from(f2.contentBase64, "base64").toString("utf8");
171673
+ }
171674
+ }
171675
+ }
171676
+ const baseTurnOpts = {
171677
+ base: odBase2,
171678
+ agentId: flags.get("agent") ?? "claude",
171679
+ artifactId: artifactId2,
171680
+ title: flags.get("title") ?? artifactId2,
171681
+ message: message2,
171682
+ ...flags.get("system") !== void 0 ? { systemPrompt: flags.get("system") } : {},
171683
+ ...isContinue ? {} : { contextFiles: contextFiles2 }
171684
+ };
171685
+ if (flags.get("async") === "true") {
171686
+ println(`[design] \u753B\u677F ${deriveBoardKey(artifactId2)} \u2190 ${artifactId2}\uFF1B${isContinue ? "\u7EED\u8F6E" : "\u9996\u8F6E"}\uFF08--async\uFF09\u2192 od \u5F15\u64CE ${odBase2} \u2026`);
171687
+ const started = await startDesignChatTurn(baseTurnOpts);
171688
+ println(`[design] \u5DF2\u4E0B\u5355\uFF1Arun ${started.runId.slice(0, 8)}\uFF08board ${started.boardKey}, \u53EF\u89C1 user \u56DE\u5408 ${started.userMessageId.slice(0, 8)}\uFF09\u3002\u5F15\u64CE\u901A\u5E38\u8981 13\u201344 \u5206\u949F\u3002`);
171689
+ println(`[design] \u4E4B\u540E\u7528 oasis design status ${artifactId2} \u67E5\u8FDB\u5EA6/\u53D6\u8D27\uFF08--pull \u62C9\u4EA7\u7269\u5230\u672C\u5730\u8BFB\uFF09\uFF1B\u4E0D\u5FC5\u5B88\u7740\u7B49\u3002`);
171690
+ break;
171691
+ }
171692
+ println(`[design] \u753B\u677F ${deriveBoardKey(artifactId2)} \u2190 ${artifactId2}\uFF1B${isContinue ? "\u7EED\u8F6E" : "\u9996\u8F6E"}\u5BF9\u8BDD\u9A71\u52A8 od \u5F15\u64CE ${odBase2} \u2026`);
171693
+ const turn = await runDesignChatTurn({
171694
+ ...baseTurnOpts,
171695
+ onLine: (line) => println(`[od] ${line.length > 500 ? line.slice(0, 500) + "\u2026" : line}`)
171696
+ });
171697
+ println(`[design] \u672C\u8F6E\u4EA4\u4ED8\uFF08board ${turn.boardKey}, run ${turn.runId.slice(0, 8)}, \u53EF\u89C1 user \u56DE\u5408 ${turn.userMessageId.slice(0, 8)}\uFF09\uFF1A`);
171698
+ for (const a of turn.artifacts) println(` - ${a.path}\uFF08${a.kind ?? "?"}\uFF09status=${a.status ?? "?"}${a.title ? ` \xB7 ${a.title}` : ""}`);
171699
+ println(`[design] \u8BFB\u4EA7\u7269\u540E\u81EA\u884C\u51B3\u5B9A\uFF1A\u591F\u597D \u2192 oasis propose\uFF1B\u6709\u5206\u6B67/\u7F3A\u4FE1\u606F \u2192 oasis gap\uFF1B\u4E0D\u591F \u2192 \u518D\u4E00\u8F6E oasis design chat ${artifactId2} --turn continue --message "<\u4E0B\u4E00\u53E5>"`);
171700
+ break;
171701
+ }
171702
+ if (sub === "status") {
171703
+ const artifactId2 = needPos(positional, 1, "oasis design status <artifactId> [--pull]");
171704
+ const odBase2 = (flags.get("od-url") ?? process.env["OASIS_OPEN_DESIGN_URL"] ?? "http://127.0.0.1:7457").replace(/\/$/, "");
171705
+ const st = await fetchBoardStatus(odBase2, fetch, artifactId2);
171706
+ if (!st.boardExists) {
171707
+ println(`[design] \u753B\u677F ${st.boardKey} \u5C1A\u4E0D\u5B58\u5728\u2014\u2014\u8FD8\u6CA1\u8D77\u8FC7\u4EFB\u4F55\u4E00\u8F6E\u3002\u5F00\u5DE5\uFF1Aoasis design chat ${artifactId2} --message "<\u8BBE\u8BA1\u9700\u6C42+\u771F\u5B9E\u6570\u636E>" --async`);
171708
+ break;
171709
+ }
171710
+ const live = st.runs.filter((r) => r.status === "running" || r.status === "queued");
171711
+ if (live.length > 0) {
171712
+ for (const r of live) println(`[design] \u5F15\u64CE\u5728\u8DD1\uFF1Arun ${r.runId.slice(0, 8)} \u72B6\u6001=${r.status}\u2014\u2014\u522B\u518D\u8D77\u65B0\u8F6E\uFF1B\u8FC7\u51E0\u5206\u949F\u518D status\uFF0C\u6216\u672C\u8F6E\u6536\u5DE5\u7B49\u91CD\u6D3E\u3002`);
171713
+ } else if (st.runs.length > 0) {
171714
+ for (const r of st.runs.slice(0, 3)) println(`[design] \u8FD1\u671F run\uFF1A${r.runId.slice(0, 8)} \u7EC8\u6001=${r.status}`);
171715
+ } else {
171716
+ println(`[design] \u65E0\u5728\u4E16 run \u8BB0\u5F55\uFF08od \u91CD\u542F\u540E\u4E3A\u7A7A\uFF0C\u5C5E\u6B63\u5E38\uFF09\u2014\u2014\u770B\u4E0B\u9762\u677F\u4E0A\u4EA7\u7269\u5373\u6301\u4E45\u771F\u76F8\u3002`);
171717
+ }
171718
+ if (st.artifacts.length === 0) {
171719
+ println(`[design] \u677F\u4E0A\u8FD8\u6CA1\u6709\u4EA4\u4ED8\u7269\u3002${live.length > 0 ? "\u7B49\u5F15\u64CE\u8DD1\u5B8C\u518D\u6765\u3002" : `\u8D77\u4E00\u8F6E\uFF1Aoasis design chat ${artifactId2} --message "<\u2026>" --async`}`);
171720
+ break;
171721
+ }
171722
+ println(`[design] \u677F\u4E0A\u4EA4\u4ED8\u7269\uFF08\u6301\u4E45\uFF0C\u65B0\u2192\u65E7\uFF09\uFF1A`);
171723
+ for (const a of st.artifacts) println(` - ${a.path}\uFF08${a.kind ?? "?"}\uFF09status=${a.status ?? "?"}${a.mtime ? ` \xB7 ${new Date(a.mtime).toISOString()}` : ""}`);
171724
+ if (flags.get("pull") === "true") {
171725
+ const odPublic2 = (flags.get("od-public-url") ?? process.env["OASIS_OPEN_DESIGN_PUBLIC_URL"] ?? "https://design.open-friday.com").replace(/\/$/, "");
171726
+ const { dir: dir3, entry: entry2 } = await harvestBoard(odBase2, fetch, odPublic2, os8.tmpdir(), st.boardKey, st.runs[0]?.runId ?? "status-pull");
171727
+ println(`[design] \u5DF2\u62C9\u5230\u672C\u5730\uFF1A${dir3}\uFF08\u5165\u53E3 ${entry2}\uFF09`);
171728
+ println(`[design] \u8BFB\u5B8C\u6EE1\u610F\u5373\u4EA4\u7A3F\uFF1Aoasis propose ${artifactId2} --from-dir ${dir3} --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"`);
171729
+ } else {
171730
+ println(`[design] \u53D6\u8D27\uFF1Aoasis design status ${artifactId2} --pull\uFF08\u62C9\u5230\u672C\u5730\u76EE\u5F55\u8BFB\uFF0C\u8BFB\u5B8C propose --from-dir \u4EA4\u7A3F\uFF09`);
171731
+ }
171732
+ break;
171733
+ }
171734
+ if (sub !== "run") throw new Error("\u7528\u6CD5\uFF1Aoasis design run <artifactId> \u2026\uFF1Boasis design chat <artifactId> --message <t> [--turn first|continue] [--async]\uFF1Boasis design status <artifactId> [--pull]");
171408
171735
  const artifactId = needPos(positional, 1, "oasis design run <artifactId>");
171409
171736
  const odBase = (flags.get("od-url") ?? process.env["OASIS_OPEN_DESIGN_URL"] ?? "http://127.0.0.1:7457").replace(/\/$/, "");
171410
171737
  const odPublic = (flags.get("od-public-url") ?? process.env["OASIS_OPEN_DESIGN_PUBLIC_URL"] ?? "https://design.open-friday.com").replace(/\/$/, "");
@@ -171425,7 +171752,7 @@ ${res.warning}`);
171425
171752
  artifactId,
171426
171753
  title: flags.get("title") ?? artifactId,
171427
171754
  message: brief,
171428
- systemPrompt: "\u4F60\u662F\u8BBE\u8BA1\u5F15\u64CE\u4E2D\u7684\u754C\u9762\u8BBE\u8BA1\u5E08\u3002\u4EFB\u52A1\u4E66\u89C1\u672C\u6761\u6D88\u606F\uFF1B\u4E0A\u6E38\u8F93\u5165\u7B49\u4E0A\u4E0B\u6587\u6587\u4EF6\u5728\u9879\u76EE task-context/ \u76EE\u5F55\u3002\u6309\u4EFB\u52A1\u4E66\u7684\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u534F\u8BAE\u5DE5\u4F5C\uFF1A\u4EA7\u51FA\u81EA\u5305\u542B HTML \u5165\u53E3\u7A3F\u4E0E acceptance.md\uFF0C\u5B8C\u6210\u5373\u7ED3\u675F\u56DE\u590D\uFF0C\u4EA4\u7A3F\u7531\u7CFB\u7EDF\u4EE3\u529E\u3002",
171755
+ systemPrompt: flags.get("system") ?? LEGACY_DESIGN_SYSTEM_PROMPT,
171429
171756
  contextFiles,
171430
171757
  onLine: (line) => println(`[od] ${line.length > 500 ? line.slice(0, 500) + "\u2026" : line}`)
171431
171758
  });
@@ -171502,6 +171829,18 @@ ${res.warning}`);
171502
171829
  println(message);
171503
171830
  break;
171504
171831
  }
171832
+ case "record-action": {
171833
+ const artifactId = needPos(positional, 0, "oasis record-action <artifactId> --name <\u52A8\u4F5C\u540D> [--pass true] [--result <json>] [--params <json>]");
171834
+ const result = flags.get("result") !== void 0 ? JSON.parse(flags.get("result")) : { pass: flags.get("pass") === "true" };
171835
+ const { message } = await api.cmd("toolCall", {
171836
+ artifactId,
171837
+ name: need(flags, "name"),
171838
+ ...flags.get("params") !== void 0 ? { params: JSON.parse(flags.get("params")) } : {},
171839
+ result
171840
+ });
171841
+ println(message);
171842
+ break;
171843
+ }
171505
171844
  case "reply": {
171506
171845
  const { message } = await api.cmd("reply", {
171507
171846
  annotationId: needPos(positional, 0, "oasis reply <annotationId> --body <\u7B54\u590D>"),
@@ -172064,12 +172403,12 @@ ${res.warning}`);
172064
172403
  init_src6();
172065
172404
 
172066
172405
  // src/index.ts
172067
- var PKG_VERSION = true ? "0.1.60" : "dev";
172068
- var OASIS_DIR = path21.join(os10.homedir(), ".oasis");
172406
+ var PKG_VERSION = true ? "0.1.62" : "dev";
172407
+ var OASIS_DIR = path21.join(os9.homedir(), ".oasis");
172069
172408
  var CONFIG_FILE = path21.join(OASIS_DIR, "node-config.json");
172070
172409
  var PID_FILE = path21.join(OASIS_DIR, "node.pid");
172071
172410
  var BIN_FILE = path21.join(OASIS_DIR, "bin", "oasis.js");
172072
- var LOCAL_BIN = path21.join(os10.homedir(), ".local", "bin", "oasis");
172411
+ var LOCAL_BIN = path21.join(os9.homedir(), ".local", "bin", "oasis");
172073
172412
  var LOG_FILE = path21.join(OASIS_DIR, "node.log");
172074
172413
  var NPM_PREFIX = path21.join(OASIS_DIR, "npm-global");
172075
172414
  var PKG_NAME = "oasis_test";
@@ -172119,7 +172458,7 @@ exec node "${BIN_FILE}" "$@"
172119
172458
  }
172120
172459
  function setupAutostart() {
172121
172460
  const cmd = `${process.execPath} "${BIN_FILE}" --_daemon`;
172122
- const home = os10.homedir();
172461
+ const home = os9.homedir();
172123
172462
  const extraBins = [
172124
172463
  path21.join(home, ".npm-global", "bin"),
172125
172464
  path21.join(home, ".local", "bin"),
@@ -172127,7 +172466,7 @@ function setupAutostart() {
172127
172466
  ];
172128
172467
  const daemonPath = [process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":");
172129
172468
  if (process.platform === "linux") {
172130
- const unitDir = path21.join(os10.homedir(), ".config", "systemd", "user");
172469
+ const unitDir = path21.join(os9.homedir(), ".config", "systemd", "user");
172131
172470
  fs26.mkdirSync(unitDir, { recursive: true });
172132
172471
  fs26.writeFileSync(path21.join(unitDir, "oasis-node.service"), [
172133
172472
  "[Unit]",
@@ -172151,7 +172490,7 @@ function setupAutostart() {
172151
172490
  } catch {
172152
172491
  }
172153
172492
  } else if (process.platform === "darwin") {
172154
- const plist = path21.join(os10.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
172493
+ const plist = path21.join(os9.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
172155
172494
  fs26.mkdirSync(path21.dirname(plist), { recursive: true });
172156
172495
  fs26.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
172157
172496
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -172314,7 +172653,7 @@ void (async () => {
172314
172653
  const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
172315
172654
  method: "POST",
172316
172655
  headers: { "content-type": "application/json" },
172317
- body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os10.hostname() })
172656
+ body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os9.hostname() })
172318
172657
  });
172319
172658
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
172320
172659
  const body = await res.json();