oasis_test 0.1.60 → 0.1.61

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 +611 -405
  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,18 @@ 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\uFF1A`,
3864
3871
  ``,
3865
- ` oasis design run ${artifactId} --reason "<\u4E00\u53E5\u8BDD\u8BF4\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"`,
3872
+ ` oasis design chat ${artifactId} --message "<\u8FD9\u4E00\u8F6E\u8981\u5F15\u64CE\u505A\u4EC0\u4E48\uFF1A\u8BBE\u8BA1\u9700\u6C42 + \u771F\u5B9E\u6570\u636E>"`,
3873
+ ` oasis design chat ${artifactId} --turn continue --message "<\u8BFB\u5B8C\u4E0A\u4E00\u7248\u4EA7\u7269\u540E\uFF0C\u8981\u6539\u54EA\u91CC>"`,
3866
3874
  ``,
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`
3875
+ `**\u6BCF\u4E00\u8F6E\u7684\u95ED\u73AF\uFF08\u522B\u8DF3\u6B65\uFF09**\uFF1A`,
3876
+ `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`,
3877
+ `2. **\u8BFB\u4EA7\u7269**\uFF1A\u547D\u4EE4\u8DD1\u5B8C\u4F1A\u6253\u5370\u672C\u8F6E\u4EA4\u4ED8\u7684 artifact \u6E05\u5355\uFF08\`index.html\` / \`acceptance.md\` \u53CA status\uFF09\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`,
3878
+ `3. **\u51B3\u5B9A**\uFF1A\u591F\u597D \u2192 \`oasis propose ${artifactId} --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"\` \u4EA4\u7A3F\uFF1B\u8FD8\u5DEE \u2192 \`oasis design chat ${artifactId} --turn continue --message "<\u5177\u4F53\u6539\u4EC0\u4E48>"\` **\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`,
3879
+ `- **\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`,
3880
+ `- \u5F15\u64CE\u4E00\u8F6E\u8981 13\u201344 \u5206\u949F\uFF08\u771F\u5728\u753B\u5E03\u4E0A\u753B\uFF09\uFF0C\`oasis design chat\` \u5185\u90E8\u5DF2\u8F6E\u8BE2 30 \u5206\u949F\u3002**\u522B\u7528 \`timeout\` \u5305\u5B83\u3001\u522B 15 \u5206\u949F\u5C31\u5F53\u5B83\u5931\u8D25**\u2014\u2014\u8010\u5FC3\u7B49\u5B83\u8DD1\u5B8C\uFF1B`,
3881
+ `- \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
3882
  ] : (() => {
3872
3883
  const codeRepo = args.codeRepo ?? null;
3873
3884
  const repos = typeDef?.contentType === "code" ? codeRepo?.repos : void 0;
@@ -3922,7 +3933,7 @@ async function assembleContext(args) {
3922
3933
  ``,
3923
3934
  ...canvasMode ? [
3924
3935
  `## \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`
3936
+ `oasis CLI \u5DF2\u6CE8\u5165\uFF08\`OASIS_SERVER\` / \`OASIS_TOKEN\` \u5C31\u7EEA\uFF0C\u65E0\u9700\u914D\u7F6E\uFF09\u2014\u2014\u4F60\u7528 \`oasis design chat\` \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
3937
  ] : commandReference({
3927
3938
  artifactId,
3928
3939
  action: args.action ?? "produce",
@@ -3950,12 +3961,14 @@ async function assembleContext(args) {
3950
3961
  (s2) => `- ${s2.upstream}: pin=${s2.pinned ?? "\u2205"} \u2192 \u6700\u65B0 ${s2.latest}${s2.upstreamCancelled ? "\uFF08\u26A0 \u4E0A\u6E38\u5DF2 cancelled\uFF09" : ""}`
3951
3962
  )
3952
3963
  ] : [],
3953
- // 画板模式:守则的 oasis 命令(gap/annotate/propose/add-revision)内层引擎全不可用——换画板版守则
3964
+ // 画板模式:你在通用 runtime 上、CLI 齐全(design chat/propose/gap 都能用);引擎里的内层 agent 才没 CLI,那层不用你管
3954
3965
  ...canvasMode ? [
3955
3966
  ``,
3956
3967
  `## \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`,
3968
+ `- \u4F60\u662F\u7532\u65B9\uFF1A\u591A\u8F6E \`oasis design chat\` \u9A71\u52A8\u5F15\u64CE\uFF0C\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`,
3969
+ `- \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
3970
  `- \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`,
3971
+ `- \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
3972
  `- \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
3973
  ] : [],
3961
3974
  // review 会话不产出、不该给生产者守则(它只投票,守则的 propose/conclude/gap/add-revision 全不适用)
@@ -4228,7 +4241,7 @@ var init_dispatcher = __esm({
4228
4241
  }
4229
4242
  };
4230
4243
  SEED_ROOT_SUCCESSFUL_PRODUCE_LIMIT = 5;
4231
- SELF_HEALING_EXITS = /* @__PURE__ */ new Set(["rate-limit"]);
4244
+ SELF_HEALING_EXITS = /* @__PURE__ */ new Set(["rate-limit", "session-not-found"]);
4232
4245
  CANVAS_RUNTIME_KIND = "open-design";
4233
4246
  NON_PRODUCE_FALLBACK_RUNTIME_KIND = "claude-code";
4234
4247
  Dispatcher = class {
@@ -4284,7 +4297,8 @@ var init_dispatcher = __esm({
4284
4297
  }
4285
4298
  /** ann: 讨论/回踢 resume 用:该 artifact 最近一次 produce/integrate 会话的 runtimeSessionId */
4286
4299
  retainedProduceSessionFor(artifactId) {
4287
- return decodeProduceSession(this.produceSessions.get(`produce:${artifactId}`))?.rtId;
4300
+ const rec = decodeProduceSession(this.produceSessions.get(`produce:${artifactId}`));
4301
+ return rec?.sessionMissing ? void 0 : rec?.rtId;
4288
4302
  }
4289
4303
  emitJournal(entry) {
4290
4304
  try {
@@ -4681,8 +4695,8 @@ var init_dispatcher = __esm({
4681
4695
  const sessionKey = this.produceSessionKeyFor(spec);
4682
4696
  const prior = sessionKey ? decodeProduceSession(this.produceSessions.get(sessionKey)) : void 0;
4683
4697
  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;
4698
+ const retainedPartSession = spec.action === "produce" && spec.part !== void 0 && priorConsistent && !prior.sessionMissing ? prior.rtId : void 0;
4699
+ const retainedWholeSession = spec.action === "produce" && spec.part === void 0 && priorConsistent && !prior.sessionMissing ? this.resumableWholeSession(spec.artifactId, seqNow, prior.rtId) : void 0;
4686
4700
  const retainedSession = retainedPartSession ?? retainedWholeSession;
4687
4701
  const runtimeSessionId = retainedSession ?? (isProduceLike ? (0, import_node_crypto3.randomUUID)() : void 0);
4688
4702
  const resumeSession = retainedSession !== void 0;
@@ -4856,11 +4870,12 @@ var init_dispatcher = __esm({
4856
4870
  const prev = decodeProduceSession(this.produceSessions.get(exitSessionKey));
4857
4871
  if (prev) {
4858
4872
  const isWholeProduce = spec.action === "produce" && spec.part === void 0;
4873
+ const sessionNotFound = info.reason === "session-not-found";
4859
4874
  try {
4860
4875
  this.produceSessions.set(exitSessionKey, encodeProduceSession({
4861
4876
  ...prev,
4862
4877
  ...info.runtimeSessionId ? { rtId: info.runtimeSessionId } : {},
4863
- ...isWholeProduce ? { seqAtExit } : { seqAtExit: void 0 },
4878
+ ...sessionNotFound ? { sessionMissing: true, seqAtExit: void 0 } : { sessionMissing: void 0, ...isWholeProduce ? { seqAtExit } : { seqAtExit: void 0 } },
4864
4879
  lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
4865
4880
  }));
4866
4881
  } catch {
@@ -137781,11 +137796,15 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
137781
137796
  }
137782
137797
  case "conclude": {
137783
137798
  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
- };
137799
+ const result = await kernel.conclude({ artifactId, actor, note: optStr(args, "note") });
137800
+ if (result.kind === "gate-pending") {
137801
+ const pending = pendingConcludeAttempt(kernel.model, artifactId);
137802
+ return { message: `conclude \u5DF2\u53D1\u8D77\uFF0Cgate \u5F85\u5BA1\uFF08quorum=${pending.gate.quorum}\uFF0Celigible=${pending.gate.eligible.join(", ")}\uFF09` };
137803
+ }
137804
+ if (result.kind === "noop-concluded") {
137805
+ 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` };
137806
+ }
137807
+ return { message: `concluded\uFF1A\u91CC\u7A0B\u7891\u6210\u7ACB\uFF08head=${result.head}\uFF09` };
137789
137808
  }
137790
137809
  case "review": {
137791
137810
  const revisionArg = optStr(args, "revisionId");
@@ -138416,7 +138435,7 @@ async function startOasisServer(opts) {
138416
138435
  res.writeHead(status, { "content-type": "application/json" }).end(
138417
138436
  JSON.stringify({
138418
138437
  error: err instanceof Error ? err.message : String(err),
138419
- ...err instanceof FrozenError ? { retryable: true } : {}
138438
+ ...err instanceof FrozenError ? { retryable: err.retryable } : {}
138420
138439
  })
138421
138440
  );
138422
138441
  }
@@ -139078,10 +139097,13 @@ async function startOasisServer(opts) {
139078
139097
  ...draftWorkspace ? { draftWorkspace } : {}
139079
139098
  });
139080
139099
  const effectiveSessionId = persistTarget ? runtimeChanged ? void 0 : persistTarget.runtimeSessionId ?? void 0 : body.sessionId || void 0;
139100
+ 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 })) : [];
139101
+ const fallbackMessage = resumeFallbackHistory.length ? buildRuntimeChatPrompt({ userMessage: persistedUserMessage, history: resumeFallbackHistory, ...draftWorkspace ? { draftWorkspace } : {} }) : void 0;
139081
139102
  const session = await opts.dispatchChat({
139082
139103
  actorId: body.actorId,
139083
139104
  message: runtimeMessage,
139084
139105
  ...effectiveSessionId ? { sessionId: effectiveSessionId } : {},
139106
+ ...fallbackMessage ? { fallbackMessage } : {},
139085
139107
  ...body.chatSessionId ? { chatSessionId: body.chatSessionId } : {},
139086
139108
  ...body.workspace ? { workspace: body.workspace } : {},
139087
139109
  ...currentCompanyId !== void 0 ? { companyId: currentCompanyId } : {},
@@ -146742,6 +146764,17 @@ function materializeContext(resolvedDir, files) {
146742
146764
  fs8.writeFileSync(f2, content);
146743
146765
  }
146744
146766
  }
146767
+ async function putVisibleUserTurn(base, fetchImpl, args) {
146768
+ const messageId = (0, import_node_crypto11.randomUUID)();
146769
+ await odApi(
146770
+ base,
146771
+ fetchImpl,
146772
+ "PUT",
146773
+ `/api/projects/${encodeURIComponent(args.boardKey)}/conversations/${encodeURIComponent(args.conversationId)}/messages/${messageId}`,
146774
+ { id: messageId, role: "user", content: args.content }
146775
+ );
146776
+ return messageId;
146777
+ }
146745
146778
  async function startDesignRun(base, fetchImpl, args) {
146746
146779
  const created = await odApi(base, fetchImpl, "POST", "/api/runs", {
146747
146780
  agentId: args.agentId,
@@ -146750,10 +146783,22 @@ async function startDesignRun(base, fetchImpl, args) {
146750
146783
  assistantMessageId: (0, import_node_crypto11.randomUUID)(),
146751
146784
  clientRequestId: (0, import_node_crypto11.randomUUID)(),
146752
146785
  message: args.message,
146753
- systemPrompt: args.systemPrompt
146786
+ // 提案 design-conversational-driving 坑②:systemPrompt 从写死改为可选——缺省用引擎侧默认,
146787
+ // 让设计意图由驱动方每轮的话(message)表达,而非焊死一句。
146788
+ ...args.systemPrompt !== void 0 ? { systemPrompt: args.systemPrompt } : {}
146754
146789
  });
146755
146790
  return created.runId;
146756
146791
  }
146792
+ async function fetchResultPackage(base, fetchImpl, runId) {
146793
+ const body = await odApi(base, fetchImpl, "GET", `/api/runs/${encodeURIComponent(runId)}/result-package`);
146794
+ const artifacts = (body.artifacts ?? []).map((a) => ({
146795
+ path: a.path ?? "",
146796
+ kind: a.kind ?? null,
146797
+ title: a.title ?? null,
146798
+ status: a.status ?? null
146799
+ }));
146800
+ return { artifacts, fileCount: body.project?.fileCount ?? artifacts.length };
146801
+ }
146757
146802
  async function pumpDesignEvents(base, fetchImpl, runId, onLine) {
146758
146803
  const res = await fetchImpl(`${base}/api/runs/${runId}/events`);
146759
146804
  if (!res.ok || !res.body) return;
@@ -146837,7 +146882,7 @@ async function runOpenDesignSession(opts) {
146837
146882
  boardKey,
146838
146883
  conversationId,
146839
146884
  message: opts.message,
146840
- systemPrompt: opts.systemPrompt
146885
+ ...opts.systemPrompt !== void 0 ? { systemPrompt: opts.systemPrompt } : {}
146841
146886
  });
146842
146887
  if (opts.onLine) void pumpDesignEvents(opts.base, fetchImpl, runId, opts.onLine).catch(() => {
146843
146888
  });
@@ -146850,6 +146895,30 @@ async function runOpenDesignSession(opts) {
146850
146895
  const { dir, entry } = await harvestBoard(opts.base, fetchImpl, opts.publicBase, workRoot, boardKey, runId);
146851
146896
  return { dir, entry, boardKey, runId };
146852
146897
  }
146898
+ async function runDesignChatTurn(opts) {
146899
+ const fetchImpl = opts.fetchImpl ?? fetch;
146900
+ const boardKey = deriveBoardKey(opts.artifactId);
146901
+ const { conversationId, resolvedDir } = await ensureProject(opts.base, fetchImpl, boardKey, opts.title);
146902
+ if (opts.contextFiles) materializeContext(resolvedDir, opts.contextFiles);
146903
+ const userMessageId = await putVisibleUserTurn(opts.base, fetchImpl, { boardKey, conversationId, content: opts.message });
146904
+ const runId = await startDesignRun(opts.base, fetchImpl, {
146905
+ agentId: opts.agentId,
146906
+ boardKey,
146907
+ conversationId,
146908
+ message: opts.message,
146909
+ ...opts.systemPrompt !== void 0 ? { systemPrompt: opts.systemPrompt } : {}
146910
+ });
146911
+ if (opts.onLine) void pumpDesignEvents(opts.base, fetchImpl, runId, opts.onLine).catch(() => {
146912
+ });
146913
+ const status = await pollDesignRun(opts.base, fetchImpl, runId, {
146914
+ ...opts.pollIntervalMs !== void 0 ? { intervalMs: opts.pollIntervalMs } : {},
146915
+ ...opts.pollMaxMs !== void 0 ? { maxMs: opts.pollMaxMs } : {}
146916
+ });
146917
+ if (status === "canceled") throw new Error(`od run ${runId} \u88AB\u53D6\u6D88`);
146918
+ if (status === "failed") throw new Error(`od run ${runId} \u5931\u8D25`);
146919
+ const { artifacts, fileCount } = await fetchResultPackage(opts.base, fetchImpl, runId);
146920
+ return { boardKey, conversationId, runId, userMessageId, artifacts, fileCount };
146921
+ }
146853
146922
  var import_node_crypto11, fs8, os, path7;
146854
146923
  var init_driver = __esm({
146855
146924
  "../adapters/src/open-design/driver.ts"() {
@@ -146892,6 +146961,25 @@ var init_sync_skills = __esm({
146892
146961
  }
146893
146962
  });
146894
146963
 
146964
+ // ../adapters/src/_core/resume-classify.ts
146965
+ function isSessionNotFoundError(text) {
146966
+ if (!text) return false;
146967
+ const t = text.toLowerCase();
146968
+ return SESSION_NOT_FOUND_PATTERNS.some((p2) => t.includes(p2));
146969
+ }
146970
+ var SESSION_NOT_FOUND_PATTERNS;
146971
+ var init_resume_classify = __esm({
146972
+ "../adapters/src/_core/resume-classify.ts"() {
146973
+ "use strict";
146974
+ SESSION_NOT_FOUND_PATTERNS = [
146975
+ "no conversation found",
146976
+ // claude: `claude --resume <缺失的 id>`
146977
+ "no rollout found for thread id"
146978
+ // codex: `codex exec resume <缺失的 uuid>`
146979
+ ];
146980
+ }
146981
+ });
146982
+
146895
146983
  // ../adapters/src/_core/session-paths.ts
146896
146984
  function resolveWorkRoot(workRoot) {
146897
146985
  if (workRoot) return workRoot;
@@ -147219,29 +147307,17 @@ function classifyExit(result) {
147219
147307
  if (result.is_error === true || String(result.subtype ?? "").startsWith("error")) return "error";
147220
147308
  return void 0;
147221
147309
  }
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;
147310
+ var import_node_child_process7, import_node_crypto13, fs10, path9, readline, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
147235
147311
  var init_claude_code = __esm({
147236
147312
  "../adapters/src/claude-code/index.ts"() {
147237
147313
  "use strict";
147238
147314
  import_node_child_process7 = require("node:child_process");
147239
147315
  import_node_crypto13 = require("node:crypto");
147240
147316
  fs10 = __toESM(require("node:fs"), 1);
147241
- os3 = __toESM(require("node:os"), 1);
147242
147317
  path9 = __toESM(require("node:path"), 1);
147243
147318
  readline = __toESM(require("node:readline"), 1);
147244
147319
  init_sync_skills();
147320
+ init_resume_classify();
147245
147321
  init_session_paths();
147246
147322
  ONE_SHOT_UNSAFE_TOOLS = ["ScheduleWakeup", "CronCreate", "CronDelete", "CronList"];
147247
147323
  ClaudeCodeAdapter = class {
@@ -147315,8 +147391,7 @@ var init_claude_code = __esm({
147315
147391
  const prompt = job.bundle.files["TASK.md"];
147316
147392
  if (!prompt) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
147317
147393
  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] : [];
147394
+ const runtimeSessionArgs = job.runtimeSessionId ? job.resumeRuntimeSession ? ["--resume", job.runtimeSessionId] : ["--session-id", job.runtimeSessionId] : [];
147320
147395
  const oneShotSafety = this.opts.oneShotSafety !== false;
147321
147396
  const args = [
147322
147397
  "-p",
@@ -147441,7 +147516,12 @@ var init_claude_code = __esm({
147441
147516
  child.on("exit", (code) => {
147442
147517
  clearTimeout(timer);
147443
147518
  if (exited) return;
147444
- const reason = killReason ?? classifyExit(lastResult);
147519
+ let reason = killReason ?? classifyExit(lastResult);
147520
+ const resultErrors = Array.isArray(lastResult?.errors) ? lastResult.errors.map(String) : [];
147521
+ const resumeErrText = [stderrTail.join("\n"), String(lastResult?.result ?? ""), ...resultErrors].join("\n");
147522
+ if (!killReason && job.resumeRuntimeSession && reason !== "clean" && isSessionNotFoundError(resumeErrText)) {
147523
+ reason = "session-not-found";
147524
+ }
147445
147525
  const workdirRef = dir;
147446
147526
  const isOneShot = job.workdirKey ? false : !job.runtimeSessionId;
147447
147527
  unlockWorkdir(dir);
@@ -147652,7 +147732,7 @@ var init_resilient = __esm({
147652
147732
 
147653
147733
  // ../adapters/src/_core/skill-cache.ts
147654
147734
  function defaultCacheRoot() {
147655
- return path10.join(os4.homedir(), ".oasis", "skill-cache");
147735
+ return path10.join(os3.homedir(), ".oasis", "skill-cache");
147656
147736
  }
147657
147737
  function sessionSkillsSubdir(runtimeKind) {
147658
147738
  switch (runtimeKind) {
@@ -147895,12 +147975,12 @@ async function syncSessionSymlinks(sessionDir, runtimeKind, actorDir, manifest,
147895
147975
  }
147896
147976
  }
147897
147977
  }
147898
- var fsp, os4, path10, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
147978
+ var fsp, os3, path10, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
147899
147979
  var init_skill_cache = __esm({
147900
147980
  "../adapters/src/_core/skill-cache.ts"() {
147901
147981
  "use strict";
147902
147982
  fsp = __toESM(require("node:fs/promises"), 1);
147903
- os4 = __toESM(require("node:os"), 1);
147983
+ os3 = __toESM(require("node:os"), 1);
147904
147984
  path10 = __toESM(require("node:path"), 1);
147905
147985
  MANIFEST_SCHEMA_VERSION = 1;
147906
147986
  STALE_LOCK_MS = 6e4;
@@ -148271,7 +148351,7 @@ function codexSessionsRoot() {
148271
148351
  const candidates = [];
148272
148352
  const ch = process.env["CODEX_HOME"];
148273
148353
  if (ch) candidates.push(path11.join(ch, "sessions"));
148274
- const home = process.env["HOME"] || os5.homedir();
148354
+ const home = process.env["HOME"] || os4.homedir();
148275
148355
  if (home) candidates.push(path11.join(home, ".codex", "sessions"));
148276
148356
  for (const c of candidates) {
148277
148357
  try {
@@ -148281,31 +148361,6 @@ function codexSessionsRoot() {
148281
148361
  }
148282
148362
  return "";
148283
148363
  }
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
148364
  function listRecentRollouts(root, sinceMs) {
148310
148365
  const margin = sinceMs - 6e4;
148311
148366
  const found = [];
@@ -148547,17 +148602,18 @@ function normalizeCodexConsoleLine(line, state = createCodexNormalizeState()) {
148547
148602
  tool.output.push(line);
148548
148603
  return [];
148549
148604
  }
148550
- var import_node_child_process9, import_node_crypto14, fs11, os5, path11, readline2, CodexAdapter;
148605
+ var import_node_child_process9, import_node_crypto14, fs11, os4, path11, readline2, CodexAdapter;
148551
148606
  var init_codex = __esm({
148552
148607
  "../adapters/src/codex/index.ts"() {
148553
148608
  "use strict";
148554
148609
  import_node_child_process9 = require("node:child_process");
148555
148610
  import_node_crypto14 = require("node:crypto");
148556
148611
  fs11 = __toESM(require("node:fs"), 1);
148557
- os5 = __toESM(require("node:os"), 1);
148612
+ os4 = __toESM(require("node:os"), 1);
148558
148613
  path11 = __toESM(require("node:path"), 1);
148559
148614
  readline2 = __toESM(require("node:readline"), 1);
148560
148615
  init_sync_skills();
148616
+ init_resume_classify();
148561
148617
  init_session_paths();
148562
148618
  init_claude_code();
148563
148619
  CodexAdapter = class {
@@ -148606,9 +148662,7 @@ var init_codex = __esm({
148606
148662
  const task = job.bundle.files["TASK.md"];
148607
148663
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
148608
148664
  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
- );
148665
+ const isResume = Boolean(job.resumeRuntimeSession && job.runtimeSessionId);
148612
148666
  const args = [
148613
148667
  "exec",
148614
148668
  "-C",
@@ -148744,7 +148798,8 @@ var init_codex = __esm({
148744
148798
  const sawStreamError = streamErrorMessages.length > 0;
148745
148799
  const isError = !killReason && (typeof code === "number" && code !== 0 || sawStreamError);
148746
148800
  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";
148801
+ const sessionNotFound = !killReason && isResume && isSessionNotFoundError([...streamErrorMessages, ...stdoutErrorLines, ...stderrTail].join("\n"));
148802
+ const reason = killReason ? killReason : sessionNotFound ? "session-not-found" : sawStreamError ? "error" : code === 0 ? "clean" : "error";
148748
148803
  finish({
148749
148804
  code: killReason ? null : code,
148750
148805
  reason,
@@ -153560,7 +153615,44 @@ var init_node_health = __esm({
153560
153615
  });
153561
153616
 
153562
153617
  // ../server/src/domains/nodes/routes.ts
153618
+ function daemonVersionLess(a, b2) {
153619
+ if (!a || !b2 || a === "\u2014" || b2 === "\u2014") return false;
153620
+ const parse2 = (v2) => v2.replace(/^v/, "").split(".").map((x2) => Number.parseInt(x2, 10));
153621
+ const pa = parse2(a);
153622
+ const pb = parse2(b2);
153623
+ if (pa.some(Number.isNaN) || pb.some(Number.isNaN)) return a < b2;
153624
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
153625
+ const ai = pa[i] ?? 0;
153626
+ const bi = pb[i] ?? 0;
153627
+ if (ai !== bi) return ai < bi;
153628
+ }
153629
+ return false;
153630
+ }
153563
153631
  function nodesDomain(deps) {
153632
+ async function resolveLatestDaemonVersion() {
153633
+ const envVer = (process.env["OASIS_LATEST_NODE_DAEMON_VERSION"] ?? "").trim();
153634
+ if (envVer) return { version: envVer, source: "env" };
153635
+ if (deps.fetchLatestNpmDaemonVersion) {
153636
+ try {
153637
+ const v2 = (await deps.fetchLatestNpmDaemonVersion())?.trim();
153638
+ if (v2) return { version: v2, source: "npm" };
153639
+ } catch {
153640
+ }
153641
+ }
153642
+ try {
153643
+ const here = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(__esm_import_meta_url));
153644
+ for (const rel of ["../../../../node-daemon/package.json", "../../../../../node-daemon/package.json"]) {
153645
+ try {
153646
+ const raw = (0, import_node_fs7.readFileSync)((0, import_node_path10.resolve)(here, rel), "utf8");
153647
+ const pkg = JSON.parse(raw);
153648
+ if (pkg.version) return { version: pkg.version, source: "monorepo" };
153649
+ } catch {
153650
+ }
153651
+ }
153652
+ } catch {
153653
+ }
153654
+ return { version: null, source: null };
153655
+ }
153564
153656
  let hubWired = false;
153565
153657
  function wireHub(hub) {
153566
153658
  if (hubWired) return;
@@ -153770,30 +153862,23 @@ function nodesDomain(deps) {
153770
153862
  return { status: 200, body: { ok: true } };
153771
153863
  });
153772
153864
  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 } };
153865
+ return { status: 200, body: await resolveLatestDaemonVersion() };
153788
153866
  });
153789
153867
  router.post("/api/nodes/:nodeId/update", async (req) => {
153790
153868
  const nodeId = req.params["nodeId"];
153791
153869
  if (!nodeId) return { status: 400, body: { error: "missing nodeId" } };
153792
153870
  const hub = deps.getHub();
153793
153871
  if (!hub) return { status: 503, body: { error: "node gateway \u672A\u5C31\u7EEA" } };
153872
+ const daemon = hub.connectedDaemons().find((d) => d.daemonId === nodeId);
153873
+ if (!daemon) return { status: 409, body: { error: `\u8282\u70B9 ${nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u65E0\u6CD5\u66F4\u65B0` } };
153874
+ const current = daemon.meta.daemonVersion;
153875
+ const { version: latest } = await resolveLatestDaemonVersion();
153876
+ if (latest && current && !daemonVersionLess(current, latest)) {
153877
+ return { status: 200, body: { ok: true, alreadyLatest: true, version: current } };
153878
+ }
153794
153879
  const sent = hub.dispatch(nodeId, { type: "update" });
153795
153880
  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 } };
153881
+ return { status: 200, body: { ok: true, alreadyLatest: false } };
153797
153882
  });
153798
153883
  router.delete("/api/runtimes/:runtimeId", async (req) => {
153799
153884
  const runtimeId = req.params["runtimeId"];
@@ -164189,19 +164274,19 @@ var init_trajectory = __esm({
164189
164274
 
164190
164275
  // src/index.ts
164191
164276
  var fs26 = __toESM(require("node:fs"));
164192
- var os10 = __toESM(require("node:os"));
164277
+ var os9 = __toESM(require("node:os"));
164193
164278
  var path21 = __toESM(require("node:path"));
164194
164279
  var import_node_child_process15 = require("node:child_process");
164195
164280
 
164196
164281
  // ../cli/src/cli.ts
164197
164282
  var fs25 = __toESM(require("node:fs"), 1);
164198
- var os9 = __toESM(require("node:os"), 1);
164283
+ var os8 = __toESM(require("node:os"), 1);
164199
164284
  var path20 = __toESM(require("node:path"), 1);
164200
164285
  var import_node_crypto31 = require("node:crypto");
164201
164286
 
164202
164287
  // ../cli/src/serve.ts
164203
164288
  var fs23 = __toESM(require("node:fs"), 1);
164204
- var os6 = __toESM(require("node:os"), 1);
164289
+ var os5 = __toESM(require("node:os"), 1);
164205
164290
  var path18 = __toESM(require("node:path"), 1);
164206
164291
  var import_node_crypto28 = require("node:crypto");
164207
164292
  var import_node_url4 = require("node:url");
@@ -168088,6 +168173,62 @@ function refreshConnectorSkillsInBackground(dataDir, apply) {
168088
168173
  })();
168089
168174
  }
168090
168175
  var SESSION_TOKEN_GRACE_MS = 15 * 6e4;
168176
+ function makeResumeRetryProxy(first, spawnFallback) {
168177
+ const outCbs = [];
168178
+ const telCbs = [];
168179
+ const outBuf = [];
168180
+ const telBuf = [];
168181
+ let sawOutput = false;
168182
+ let active = first;
168183
+ const fanOut = (chunk) => {
168184
+ if (outCbs.length) for (const cb of outCbs) cb(chunk);
168185
+ else outBuf.push(chunk);
168186
+ };
168187
+ const fanTel = (e) => {
168188
+ if (telCbs.length) for (const cb of telCbs) cb(e);
168189
+ else telBuf.push(e);
168190
+ };
168191
+ const wire = (s2) => {
168192
+ s2.onOutput((c) => {
168193
+ sawOutput = true;
168194
+ fanOut(c);
168195
+ });
168196
+ s2.onTelemetry?.((e) => fanTel(e));
168197
+ };
168198
+ wire(first);
168199
+ const isSessionNotFound = (err) => {
168200
+ const chatExit = err instanceof Error ? err.chatExit : void 0;
168201
+ return chatExit?.reason === "session-not-found";
168202
+ };
168203
+ const done = first.done.catch(async (err) => {
168204
+ if (sawOutput || !isSessionNotFound(err)) throw err;
168205
+ const second = await spawnFallback();
168206
+ active = second;
168207
+ wire(second);
168208
+ await second.done;
168209
+ });
168210
+ return {
168211
+ get id() {
168212
+ return active.id;
168213
+ },
168214
+ get runId() {
168215
+ return active.runId;
168216
+ },
168217
+ onOutput(cb) {
168218
+ for (const c of outBuf.splice(0)) cb(c);
168219
+ outCbs.push(cb);
168220
+ },
168221
+ onTelemetry(cb) {
168222
+ for (const e of telBuf.splice(0)) cb(e);
168223
+ telCbs.push(cb);
168224
+ },
168225
+ done,
168226
+ kill: () => active.kill?.() ?? Promise.resolve(),
168227
+ get nativeSessionId() {
168228
+ return active.nativeSessionId;
168229
+ }
168230
+ };
168231
+ }
168091
168232
  function normalizeHttpBaseUrl(raw) {
168092
168233
  const url = new URL(raw);
168093
168234
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -168111,13 +168252,37 @@ function apiUrlFromGatewayUrl(raw) {
168111
168252
  return void 0;
168112
168253
  }
168113
168254
  }
168255
+ var NODE_DAEMON_PKG = "oasis_test";
168256
+ var NPM_LATEST_TTL_MS = 5 * 6e4;
168257
+ var npmLatestCache = null;
168258
+ async function fetchLatestNpmDaemonVersion() {
168259
+ if (npmLatestCache && Date.now() - npmLatestCache.atMs < NPM_LATEST_TTL_MS) return npmLatestCache.version;
168260
+ let version2 = null;
168261
+ try {
168262
+ const ctrl = new AbortController();
168263
+ const timer = setTimeout(() => ctrl.abort(), 3e3);
168264
+ try {
168265
+ const resp = await fetch(`https://registry.npmjs.org/${NODE_DAEMON_PKG}/latest`, { signal: ctrl.signal });
168266
+ if (resp.ok) {
168267
+ const body = await resp.json();
168268
+ version2 = typeof body.version === "string" ? body.version : null;
168269
+ }
168270
+ } finally {
168271
+ clearTimeout(timer);
168272
+ }
168273
+ } catch {
168274
+ version2 = null;
168275
+ }
168276
+ if (version2) npmLatestCache = { version: version2, atMs: Date.now() };
168277
+ return version2;
168278
+ }
168114
168279
  var oasisWrapperCache;
168115
168280
  function oasisWrapperScript() {
168116
168281
  if (oasisWrapperCache && fs23.existsSync(oasisWrapperCache)) return oasisWrapperCache;
168117
168282
  const repoRoot = (0, import_node_url4.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
168118
168283
  const tsx = path18.join(repoRoot, "node_modules/.bin/tsx");
168119
168284
  const main = path18.join(repoRoot, "packages/cli/src/main.ts");
168120
- const dir = fs23.mkdtempSync(path18.join(os6.tmpdir(), "oasis-cli-"));
168285
+ const dir = fs23.mkdtempSync(path18.join(os5.tmpdir(), "oasis-cli-"));
168121
168286
  const wrapper = path18.join(dir, "oasis");
168122
168287
  fs23.writeFileSync(wrapper, `#!/usr/bin/env bash
168123
168288
  exec ${JSON.stringify(tsx)} ${JSON.stringify(main)} "$@"
@@ -168328,7 +168493,7 @@ async function startServe(opts) {
168328
168493
  `[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
168494
  );
168330
168495
  const issuer = createTokenIssuer({ secretFile: path18.join(opts.dir, "session-token-secret") });
168331
- const operatorActor = `actor:human:${os6.userInfo().username}`;
168496
+ const operatorActor = `actor:human:${os5.userInfo().username}`;
168332
168497
  const operatorToken = issuer.issue(operatorActor);
168333
168498
  fs23.writeFileSync(path18.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
168334
168499
  let nodeTokens = createNodeTokenStore(path18.join(opts.dir, "node-tokens.json"));
@@ -168814,7 +168979,8 @@ async function startServe(opts) {
168814
168979
  gatewayUrl: opts.gatewayUrl ?? `ws://127.0.0.1:${opts.port ?? 7320}/node-gateway`,
168815
168980
  repoRemote: opts.nodeRepoRemote ?? "git@github.com:open-friday/oasis-core.git",
168816
168981
  nodeHealth,
168817
- activeRunCountOf
168982
+ activeRunCountOf,
168983
+ fetchLatestNpmDaemonVersion
168818
168984
  }),
168819
168985
  collabDomain({
168820
168986
  kernel,
@@ -168893,301 +169059,309 @@ async function startServe(opts) {
168893
169059
  discussSessions: new FileSideMap(path18.join(opts.dir, "discuss-sessions.json")),
168894
169060
  escalateDiscussRetain: new FileSideMap(path18.join(opts.dir, "escalate-discuss-retain.json")),
168895
169061
  // 切片 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();
169062
+ dispatchChat: async (chatReq) => {
169063
+ const dispatchChatImpl = async ({ actorId, message, sessionId, chatSessionId, attachments, workspace, companyId }) => {
169064
+ const runCompanyId = companyId ?? defaultCompanyId;
169065
+ const actorCtxForCompany = await actors.resolveCtx(runCompanyId);
169066
+ const actorService = actorCtxForCompany.service;
169067
+ const binding = await actorService.resolveBinding()(actorId);
169068
+ if (!binding) {
169069
+ throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u6570\u5B57\u5458\u5DE5 ${actorId} \u6CA1\u6709 active runtime \u7ED1\u5B9A`);
169070
+ }
169071
+ const node = hub?.connectedDaemons().find((d) => d.daemonId === binding.nodeId);
169072
+ if (!node) {
169073
+ throw new ApiError(409, "NODE_OFFLINE", `\u7ED1\u5B9A\u8282\u70B9 ${binding.nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF`);
169074
+ }
169075
+ if (!node.meta.adapters.includes(binding.runtimeKind)) {
169076
+ throw new ApiError(409, "RUNTIME_UNAVAILABLE", `\u8282\u70B9 ${binding.nodeId} \u672A\u4E0A\u62A5\u8FD0\u884C\u65F6 ${binding.runtimeKind}`);
169077
+ }
169078
+ if (!chatRemoteAdapter) {
169079
+ throw new ApiError(503, "NODE_GATEWAY_NOT_READY", "node-gateway \u5C1A\u672A\u5C31\u7EEA");
169080
+ }
169081
+ if (!localUrl) {
169082
+ throw new ApiError(503, "SERVER_URL_NOT_READY", "server URL \u5C1A\u672A\u5C31\u7EEA");
169083
+ }
169084
+ const priorChatSession = chatSessionId ? await chatSessionStore?.getSession(chatSessionId).catch(() => null) : null;
169085
+ const freshChatWorkdir = chatSessionId ? chatNodeChanged(
169086
+ { nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
169087
+ { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
169088
+ ) : false;
169089
+ const runtimeSessionId = sessionId ?? (0, import_node_crypto28.randomUUID)();
169090
+ const resumeRuntimeSession = Boolean(sessionId);
169091
+ const traceRunId = `chat-run:${(0, import_node_crypto28.randomUUID)()}`;
169092
+ const artifactId = `artifact:chat:${(0, import_node_crypto28.randomUUID)()}`;
169093
+ const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
169094
+ let traceSeq = 0;
169095
+ let lastProgressTouchMs = 0;
169096
+ const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
169097
+ let traceEnabled = true;
169098
+ const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
169099
+ let traceChain = traceStore.createRun({
169100
+ id: traceRunId,
169101
+ actorId,
169102
+ runtimeKind: traceRuntimeKind(binding.runtimeKind),
169103
+ runtimeInstanceId: `runtime:${binding.nodeId}:${binding.runtimeKind}`,
169104
+ triggerKind: "api",
169105
+ triggerRef: runtimeSessionId,
169106
+ jobKey: runtimeSessionId,
169107
+ artifactId,
169108
+ action: "chat",
169109
+ startedAt: traceStartedAt,
169110
+ metadata: {
169111
+ runtimeSessionId,
169112
+ nodeId: binding.nodeId,
169113
+ runtimeKind: binding.runtimeKind,
169114
+ messagePreview: message.slice(0, 500),
169115
+ ...analyzedRunId ? { analyzedRunId } : {}
169116
+ }
169117
+ }).then(async () => {
169118
+ await traceStore.appendEvents(traceRunId, [
169119
+ {
169120
+ seq: ++traceSeq,
169121
+ eventType: "run.started",
169122
+ stream: "runtime",
169123
+ message: `chat ${actorId}`,
169124
+ startedAt: traceStartedAt
169125
+ },
169126
+ {
169127
+ seq: ++traceSeq,
169128
+ eventType: "input.message",
169129
+ stream: "model",
169130
+ actorId,
169131
+ message: message.slice(0, 500),
169132
+ payload: { text: message },
169133
+ startedAt: traceStartedAt
169134
+ }
169135
+ ]);
168977
169136
  }).catch((err) => {
168978
169137
  traceEnabled = false;
168979
- console.warn(`[trace] chat run ${traceRunId} failed: ${String(err)}`);
169138
+ console.warn(`[trace] chat run create failed: ${String(err)}`);
168980
169139
  });
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");
169140
+ const enqueueTrace = (step) => {
169141
+ if (!traceEnabled) return;
169142
+ traceChain = traceChain.then(async () => {
169143
+ if (traceEnabled) await step();
169144
+ }).catch((err) => {
169145
+ traceEnabled = false;
169146
+ console.warn(`[trace] chat run ${traceRunId} failed: ${String(err)}`);
169147
+ });
169148
+ };
169149
+ const provision = await buildActorProvision(actorService, actorId);
169150
+ const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
169151
+ const chatResolvedModel = (() => {
169152
+ if (actorCtx?.config?.model) return actorCtx.config.model;
169153
+ return void 0;
169154
+ })();
169155
+ const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => c?.model).catch(() => void 0) ?? void 0;
169156
+ const chatModelSource = chatResolvedModel ? "actor_config" : chatModel ? "runtime_default" : null;
169157
+ const attachmentFiles = {};
169158
+ const attachmentBinary = {};
169159
+ const attachmentPaths = [];
169160
+ const usedRel = /* @__PURE__ */ new Set();
169161
+ for (const att of attachments ?? []) {
169162
+ const safe = (att.name || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
169163
+ let rel = `inputs/attachments/${safe}`;
169164
+ let n = 1;
169165
+ while (usedRel.has(rel)) rel = `inputs/attachments/${n++}-${safe}`;
169166
+ usedRel.add(rel);
169167
+ if (typeof att.text === "string") {
169168
+ attachmentFiles[rel] = att.text;
169007
169169
  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}`)] : [],
169170
+ } else if (att.blobRef) {
169171
+ try {
169172
+ const bytes = await assets.get(att.blobRef);
169173
+ attachmentBinary[rel] = Buffer.from(bytes).toString("base64");
169174
+ attachmentPaths.push(rel);
169175
+ } catch (err) {
169176
+ console.warn(`[chat] \u9644\u4EF6 blob ${att.blobRef} \u8BFB\u53D6\u5931\u8D25: ${String(err)}`);
169177
+ }
169178
+ }
169179
+ }
169180
+ const sessionLine = chatSessionId ? `chat_session_id: ${chatSessionId}` : `runtime_session_id: ${runtimeSessionId}`;
169181
+ const taskHeader = workspace ? [
169182
+ "# \u4E0E\u5DE5\u5355\u7BA1\u7406\u8005\u5BF9\u8BDD",
169034
169183
  "",
169035
- "## \u7528\u6237\u6D88\u606F",
169184
+ sessionLine,
169185
+ `actor_id: ${actorId}`,
169186
+ `workspace: ${workspace}`,
169036
169187
  "",
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 } : {}
169188
+ CONVERSATIONAL_MANAGER_BODY
169189
+ ] : [
169190
+ "# \u5373\u65F6\u8111\u66B4\u5BF9\u8BDD",
169191
+ "",
169192
+ sessionLine,
169193
+ `actor_id: ${actorId}`,
169194
+ "",
169195
+ "\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"
169196
+ ];
169197
+ const files = {
169198
+ "TASK.md": [
169199
+ ...taskHeader,
169200
+ ...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}`)] : [],
169201
+ "",
169202
+ "## \u7528\u6237\u6D88\u606F",
169203
+ "",
169204
+ message
169205
+ ].join("\n")
169206
+ };
169207
+ Object.assign(files, attachmentFiles);
169208
+ if (actorCtx?.config?.prompt) {
169209
+ Object.assign(files, splitIdentityFiles3(actorCtx.config.prompt));
169210
+ }
169211
+ if (!SKILL_CACHE_RUNTIMES.has(binding.runtimeKind)) {
169212
+ Object.assign(files, materializeBuiltins(builtinSkills, binding.runtimeKind));
169213
+ Object.assign(files, await materializeSkillFiles({
169214
+ service: actorService,
169215
+ actorId,
169216
+ runtimeKind: binding.runtimeKind
169217
+ }).catch((err) => {
169218
+ console.warn(`[chat] skill materialize failed for ${actorId}: ${String(err)}`);
169219
+ return {};
169220
+ }));
169221
+ }
169222
+ const limits = { wallClockMs: SESSION_WALL_CLOCK_MS.chat };
169223
+ const handle = await chatRemoteAdapter.spawn({
169224
+ actor: actorId,
169225
+ actorToken: issueSessionToken(actorId, { artifactId, action: "chat", limits, binding, sessionId: runtimeSessionId, companyId: runCompanyId, ...chatSessionId !== void 0 ? { chatSessionId } : {} }),
169226
+ artifactId,
169227
+ bundle: { files, ...Object.keys(attachmentBinary).length ? { binaryFiles: attachmentBinary } : {} },
169228
+ ...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
169229
+ ...chatModel ? { model: chatModel } : {},
169230
+ // ADR-0053 D1:chat 的**会话线 id 是 chatSessionId**(Oasis 命名空间、跨轮不变),不是 rtId
169231
+ // (rtId 跨轮会变:换节点时服务端主动清掉它、codex/ACP 还会自己另分配)。
169232
+ //
169233
+ // 没有 chatSessionId → **不给 workdirKey** → 落 one-shot,这是契约的直接应用而不是折中:
169234
+ // 服务端对这种请求也不落库消息(persistTarget 要求 chatSessionId),没有会话线、没有历史,
169235
+ // 本就没什么可续的。产品路径跑不到这条分支(前端三个调用方都保证先建会话再传),只兜住
169236
+ // API 调用。
169237
+ ...chatSessionId ? { workdirKey: `chat:${chatSessionId}` } : {},
169238
+ // 与上一条成对:稳定目录一上,A→B→A 回到 node1 就会撞见同名目录里 A 自己 N 轮前的陈旧
169239
+ // 文件(而注入的历史正告诉它「B 改了 X」)。切根前没这问题纯属侥幸——那时换节点会清掉
169240
+ // rtId、rtId 又是目录名,"新目录"是副产品;稳定目录一上,侥幸就没了。
169241
+ //
169242
+ // **在这儿算,不放进 ChatDispatchRequest**:本函数是唯一同时握着 binding chatSessionId
169243
+ // 的地方,四个调用方(/api/chat、discuss、飞书入站、草案评审通知)全覆盖且谁都不用记得
169244
+ // 传。放进契约就等于要求每个调用方自己算,而其中三个压根不碰 binding。
169245
+ ...freshChatWorkdir ? { freshWorkdir: true } : {},
169246
+ runtimeSessionId,
169247
+ resumeRuntimeSession,
169248
+ server: { url: serverUrlForBinding(binding) },
169249
+ binding,
169250
+ limits,
169251
+ // OASIS_STAGE=1:chat agent 的改图命令(edit/link/spawn/cancel)经 /api/stage → 草案路由
169252
+ // (建单/改图暂存统一,见 docs/proposals/draft-edit-unification.md)。
169253
+ // OASIS_WORKSPACE:给定工单(协调者对话)时绑上——staged 编辑的 type:title 在本工单范围解析,
169254
+ // 产出的草案 plan.workspace = 本工单,前端「待确认变更」按工单过滤才抓得到。普通脑暴不传,保持不绑。
169255
+ env: {
169256
+ ...provision.env,
169257
+ OASIS_STAGE: "1",
169258
+ ...chatSessionId ? { OASIS_CHAT_SESSION_ID: chatSessionId } : {},
169259
+ ...workspace ? { OASIS_WORKSPACE: workspace } : {}
169260
+ },
169261
+ wrapperPaths: provision.wrapperPaths,
169262
+ ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {}
169108
169263
  });
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));
169264
+ const chatJobKey = `chat::${artifactId}`;
169265
+ chatLiveSessions.set(chatJobKey, { artifactId, sessionId: traceRunId, dispatchId: handle.id, kill: () => handle.kill() });
169266
+ enqueueTrace(async () => {
169267
+ const run = await traceStore.getRun(traceRunId);
169268
+ if (!run) return;
169269
+ const base = run.metadata && typeof run.metadata === "object" && !Array.isArray(run.metadata) ? run.metadata : {};
169270
+ await traceStore.updateRun(traceRunId, {
169271
+ metadata: { ...base, dispatchId: handle.id },
169272
+ // 恢复路径拿不到 dispatchChat 闭包里的 fallback 解析结果,开跑时先写进账本;退出帧若上报
169273
+ // 更精确 model 仍会覆盖为 runtime 来源。
169274
+ ...chatModel ? { effectiveModel: chatModel, modelSource: chatModelSource } : {}
169275
+ });
169276
+ });
169277
+ const buffered = [];
169278
+ const bufferedTelemetry = [];
169279
+ const outputCbs = [];
169280
+ const telemetryCbs = [];
169281
+ handle.onOutput?.((chunk) => {
169282
+ if (outputCbs.length === 0) buffered.push(chunk);
169283
+ for (const cb of outputCbs) cb(chunk);
169284
+ });
169285
+ handle.onTelemetry?.((event) => {
169286
+ if (telemetryCbs.length === 0) bufferedTelemetry.push(event);
169287
+ for (const cb of telemetryCbs) cb(event);
169288
+ if (event.kind === "system") {
169289
+ const ms = Date.parse(event.ts);
169290
+ if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
169291
+ lastProgressTouchMs = ms;
169292
+ enqueueTrace(() => traceStore.updateRun(traceRunId, { lastProgressAt: event.ts }).then(() => void 0));
169293
+ }
169294
+ return;
169126
169295
  }
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
169296
+ enqueueTrace(() => traceStore.appendEvents(traceRunId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
169297
+ });
169298
+ let capturedNativeSessionId;
169299
+ const done = new Promise((resolve5, reject) => {
169300
+ handle.onExit((info) => {
169301
+ chatLiveSessions.delete(chatJobKey);
169302
+ void provision.cleanup();
169303
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
169304
+ const durationMs = Date.parse(endedAt) - Date.parse(traceStartedAt);
169305
+ if (info.runtimeSessionId) capturedNativeSessionId = info.runtimeSessionId;
169306
+ enqueueTrace(async () => {
169307
+ await traceStore.appendEvents(traceRunId, [{
169308
+ seq: ++traceSeq,
169309
+ eventType: "run.finished",
169310
+ stream: "runtime",
169311
+ message: `exit code=${info.code ?? "killed"}`,
169312
+ startedAt: endedAt
169313
+ }]);
169314
+ const usage = info.usage ?? null;
169315
+ const effectiveModel = info.model ?? chatModel ?? null;
169316
+ const modelSource = info.model ? "runtime" : chatModelSource;
169317
+ await traceStore.updateRun(traceRunId, {
169318
+ status: info.reason === "timeout" ? "timeout" : info.reason === "cancelled" ? "cancelled" : info.code === 0 ? "succeeded" : "failed",
169319
+ endedAt,
169320
+ durationMs,
169321
+ exitCode: info.code,
169322
+ exitReason: info.reason ?? null,
169323
+ transcriptRef: info.transcriptRef ?? null,
169324
+ usage,
169325
+ effectiveModel,
169326
+ modelSource
169327
+ });
169328
+ if (usage && traceStore.updateUsageAggregates) {
169329
+ const run = await traceStore.getRun(traceRunId);
169330
+ if (run) void traceStore.updateUsageAggregates(run).catch(() => {
169331
+ });
169332
+ }
169160
169333
  });
169161
- if (usage && traceStore.updateUsageAggregates) {
169162
- const run = await traceStore.getRun(traceRunId);
169163
- if (run) void traceStore.updateUsageAggregates(run).catch(() => {
169164
- });
169334
+ const failed = info.code !== 0 || info.reason !== void 0 && info.reason !== "clean";
169335
+ if (!failed) resolve5();
169336
+ else {
169337
+ reject(buildChatExitError(info));
169165
169338
  }
169166
169339
  });
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
169340
  });
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
- }
169341
+ return {
169342
+ id: runtimeSessionId,
169343
+ runId: traceRunId,
169344
+ onOutput(cb) {
169345
+ for (const chunk of buffered.splice(0)) cb(chunk);
169346
+ outputCbs.push(cb);
169347
+ },
169348
+ onTelemetry(cb) {
169349
+ for (const event of bufferedTelemetry.splice(0)) cb(event);
169350
+ telemetryCbs.push(cb);
169351
+ },
169352
+ done,
169353
+ kill: () => handle.kill(),
169354
+ get nativeSessionId() {
169355
+ return capturedNativeSessionId;
169356
+ }
169357
+ };
169190
169358
  };
169359
+ const first = await dispatchChatImpl(chatReq);
169360
+ if (!chatReq.fallbackMessage || !chatReq.sessionId) return first;
169361
+ return makeResumeRetryProxy(
169362
+ first,
169363
+ () => dispatchChatImpl({ ...chatReq, sessionId: void 0, fallbackMessage: void 0, message: chatReq.fallbackMessage })
169364
+ );
169191
169365
  },
169192
169366
  trajectoryFeed: {
169193
169367
  subscribe(sessionId, listener) {
@@ -170673,7 +170847,7 @@ async function runCli(argv, println = console.log) {
170673
170847
  const res = await fetch(`${httpBase2}/api/nodes/exchange`, {
170674
170848
  method: "POST",
170675
170849
  headers: { "content-type": "application/json" },
170676
- body: JSON.stringify({ enrollToken, nodeId, hostname: os9.hostname() })
170850
+ body: JSON.stringify({ enrollToken, nodeId, hostname: os8.hostname() })
170677
170851
  });
170678
170852
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
170679
170853
  const body = await res.json();
@@ -170706,7 +170880,7 @@ async function runCli(argv, println = console.log) {
170706
170880
  const binPath = path20.join(prefix, "bin", "oasis_test");
170707
170881
  const svcArgs = `node --server-ws ${serverUrl} --dir ${dir}${nodeName ? ` --name ${nodeName}` : ""}`;
170708
170882
  if (process.platform === "linux") {
170709
- const svcFile = path20.join(os9.homedir(), ".config/systemd/user/oasis-node.service");
170883
+ const svcFile = path20.join(os8.homedir(), ".config/systemd/user/oasis-node.service");
170710
170884
  fs25.mkdirSync(path20.dirname(svcFile), { recursive: true });
170711
170885
  if (spawnSync("systemctl", ["--user", "is-active", "--quiet", "oasis-node"], {}).status === 0)
170712
170886
  spawnSync("systemctl", ["--user", "stop", "oasis-node"], { stdio: "inherit" });
@@ -170719,7 +170893,7 @@ async function runCli(argv, println = console.log) {
170719
170893
  `ExecStart=${binPath} ${svcArgs}`,
170720
170894
  // Carry the install-time PATH so the detached daemon detects CLIs in user dirs
170721
170895
  // (~/.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`,
170896
+ `Environment=PATH=${process.env["PATH"] ?? ""}:${path20.join(os8.homedir(), ".npm-global/bin")}:${path20.join(os8.homedir(), ".local/bin")}:/usr/local/bin`,
170723
170897
  "Restart=on-failure",
170724
170898
  "RestartSec=5",
170725
170899
  "StandardOutput=journal",
@@ -170731,23 +170905,23 @@ async function runCli(argv, println = console.log) {
170731
170905
  ].join("\n"));
170732
170906
  spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
170733
170907
  spawnSync("systemctl", ["--user", "enable", "--now", "oasis-node"], { stdio: "inherit" });
170734
- spawnSync("loginctl", ["enable-linger", os9.userInfo().username], { stdio: "ignore" });
170908
+ spawnSync("loginctl", ["enable-linger", os8.userInfo().username], { stdio: "ignore" });
170735
170909
  println("\u2713 Oasis node service started (systemd --user oasis-node)");
170736
170910
  println(" Status : systemctl --user status oasis-node");
170737
170911
  println(" Logs : journalctl --user -u oasis-node -f");
170738
170912
  println(" Stop : systemctl --user stop oasis-node");
170739
170913
  } else if (process.platform === "darwin") {
170740
- const plist = path20.join(os9.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
170914
+ const plist = path20.join(os8.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
170741
170915
  fs25.mkdirSync(path20.dirname(plist), { recursive: true });
170742
170916
  spawnSync("launchctl", ["unload", plist], { stdio: "ignore" });
170743
170917
  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>`);
170918
+ 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
170919
  spawnSync("launchctl", ["load", plist], { stdio: "inherit" });
170746
170920
  println("\u2713 Oasis node service started (macOS LaunchAgent com.oasis.node)");
170747
- println(` Logs : tail -f ${os9.homedir()}/.oasis-node.log`);
170921
+ println(` Logs : tail -f ${os8.homedir()}/.oasis-node.log`);
170748
170922
  println(` Stop : launchctl unload ${plist}`);
170749
170923
  } else {
170750
- spawnSync("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path20.join(os9.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
170924
+ spawnSync("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path20.join(os8.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
170751
170925
  println(`\u2713 Oasis node started in background (nohup). Logs: ~/.oasis-node.log`);
170752
170926
  println(" Note: no automatic restart on reboot in this environment.");
170753
170927
  }
@@ -170818,8 +170992,8 @@ async function runCli(argv, println = console.log) {
170818
170992
  if (command === "serve" && positional[0] === "install") {
170819
170993
  const absDir = path20.resolve(dir);
170820
170994
  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");
170995
+ const unitDir = flags.get("unit-dir") ?? path20.join(os8.homedir(), ".config", "systemd", "user");
170996
+ const bin = flags.get("bin") ?? path20.join(os8.homedir(), ".local", "bin", "oasis");
170823
170997
  const port = flags.get("port") ?? "7320";
170824
170998
  const extra = (flags.get("dispatch") === "true" ? ` --dispatch true` : "") + (flags.has("model") ? ` --model ${flags.get("model")}` : "");
170825
170999
  const unit = [
@@ -171404,7 +171578,39 @@ ${res.warning}`);
171404
171578
  }
171405
171579
  case "design": {
171406
171580
  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>]");
171581
+ 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";
171582
+ if (sub === "chat") {
171583
+ const artifactId2 = needPos(positional, 1, "oasis design chat <artifactId> --message <t>");
171584
+ const message2 = flags.get("message") ?? (["DESIGN_BRIEF.md", "TASK.md"].map((f2) => fs25.existsSync(f2) ? fs25.readFileSync(f2, "utf8") : "").find((s2) => s2.trim()) ?? "");
171585
+ 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");
171586
+ const odBase2 = (flags.get("od-url") ?? process.env["OASIS_OPEN_DESIGN_URL"] ?? "http://127.0.0.1:7457").replace(/\/$/, "");
171587
+ const isContinue = flags.get("turn") === "continue";
171588
+ const contextFiles2 = {};
171589
+ if (!isContinue) {
171590
+ contextFiles2["DESIGN_BRIEF.md"] = message2;
171591
+ if (fs25.existsSync("inputs") && fs25.statSync("inputs").isDirectory()) {
171592
+ for (const f2 of readDirFiles("inputs")) {
171593
+ contextFiles2[`inputs/${f2.path}`] = Buffer.from(f2.contentBase64, "base64").toString("utf8");
171594
+ }
171595
+ }
171596
+ }
171597
+ println(`[design] \u753B\u677F ${deriveBoardKey(artifactId2)} \u2190 ${artifactId2}\uFF1B${isContinue ? "\u7EED\u8F6E" : "\u9996\u8F6E"}\u5BF9\u8BDD\u9A71\u52A8 od \u5F15\u64CE ${odBase2} \u2026`);
171598
+ const turn = await runDesignChatTurn({
171599
+ base: odBase2,
171600
+ agentId: flags.get("agent") ?? "claude",
171601
+ artifactId: artifactId2,
171602
+ title: flags.get("title") ?? artifactId2,
171603
+ message: message2,
171604
+ ...flags.get("system") !== void 0 ? { systemPrompt: flags.get("system") } : {},
171605
+ ...isContinue ? {} : { contextFiles: contextFiles2 },
171606
+ onLine: (line) => println(`[od] ${line.length > 500 ? line.slice(0, 500) + "\u2026" : line}`)
171607
+ });
171608
+ 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`);
171609
+ for (const a of turn.artifacts) println(` - ${a.path}\uFF08${a.kind ?? "?"}\uFF09status=${a.status ?? "?"}${a.title ? ` \xB7 ${a.title}` : ""}`);
171610
+ 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>"`);
171611
+ break;
171612
+ }
171613
+ if (sub !== "run") throw new Error("\u7528\u6CD5\uFF1Aoasis design run <artifactId> \u2026\uFF0C\u6216 oasis design chat <artifactId> --message <t> [--turn first|continue]");
171408
171614
  const artifactId = needPos(positional, 1, "oasis design run <artifactId>");
171409
171615
  const odBase = (flags.get("od-url") ?? process.env["OASIS_OPEN_DESIGN_URL"] ?? "http://127.0.0.1:7457").replace(/\/$/, "");
171410
171616
  const odPublic = (flags.get("od-public-url") ?? process.env["OASIS_OPEN_DESIGN_PUBLIC_URL"] ?? "https://design.open-friday.com").replace(/\/$/, "");
@@ -171425,7 +171631,7 @@ ${res.warning}`);
171425
171631
  artifactId,
171426
171632
  title: flags.get("title") ?? artifactId,
171427
171633
  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",
171634
+ systemPrompt: flags.get("system") ?? LEGACY_DESIGN_SYSTEM_PROMPT,
171429
171635
  contextFiles,
171430
171636
  onLine: (line) => println(`[od] ${line.length > 500 ? line.slice(0, 500) + "\u2026" : line}`)
171431
171637
  });
@@ -172064,12 +172270,12 @@ ${res.warning}`);
172064
172270
  init_src6();
172065
172271
 
172066
172272
  // src/index.ts
172067
- var PKG_VERSION = true ? "0.1.60" : "dev";
172068
- var OASIS_DIR = path21.join(os10.homedir(), ".oasis");
172273
+ var PKG_VERSION = true ? "0.1.61" : "dev";
172274
+ var OASIS_DIR = path21.join(os9.homedir(), ".oasis");
172069
172275
  var CONFIG_FILE = path21.join(OASIS_DIR, "node-config.json");
172070
172276
  var PID_FILE = path21.join(OASIS_DIR, "node.pid");
172071
172277
  var BIN_FILE = path21.join(OASIS_DIR, "bin", "oasis.js");
172072
- var LOCAL_BIN = path21.join(os10.homedir(), ".local", "bin", "oasis");
172278
+ var LOCAL_BIN = path21.join(os9.homedir(), ".local", "bin", "oasis");
172073
172279
  var LOG_FILE = path21.join(OASIS_DIR, "node.log");
172074
172280
  var NPM_PREFIX = path21.join(OASIS_DIR, "npm-global");
172075
172281
  var PKG_NAME = "oasis_test";
@@ -172119,7 +172325,7 @@ exec node "${BIN_FILE}" "$@"
172119
172325
  }
172120
172326
  function setupAutostart() {
172121
172327
  const cmd = `${process.execPath} "${BIN_FILE}" --_daemon`;
172122
- const home = os10.homedir();
172328
+ const home = os9.homedir();
172123
172329
  const extraBins = [
172124
172330
  path21.join(home, ".npm-global", "bin"),
172125
172331
  path21.join(home, ".local", "bin"),
@@ -172127,7 +172333,7 @@ function setupAutostart() {
172127
172333
  ];
172128
172334
  const daemonPath = [process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":");
172129
172335
  if (process.platform === "linux") {
172130
- const unitDir = path21.join(os10.homedir(), ".config", "systemd", "user");
172336
+ const unitDir = path21.join(os9.homedir(), ".config", "systemd", "user");
172131
172337
  fs26.mkdirSync(unitDir, { recursive: true });
172132
172338
  fs26.writeFileSync(path21.join(unitDir, "oasis-node.service"), [
172133
172339
  "[Unit]",
@@ -172151,7 +172357,7 @@ function setupAutostart() {
172151
172357
  } catch {
172152
172358
  }
172153
172359
  } else if (process.platform === "darwin") {
172154
- const plist = path21.join(os10.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
172360
+ const plist = path21.join(os9.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
172155
172361
  fs26.mkdirSync(path21.dirname(plist), { recursive: true });
172156
172362
  fs26.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
172157
172363
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -172314,7 +172520,7 @@ void (async () => {
172314
172520
  const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
172315
172521
  method: "POST",
172316
172522
  headers: { "content-type": "application/json" },
172317
- body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os10.hostname() })
172523
+ body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os9.hostname() })
172318
172524
  });
172319
172525
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
172320
172526
  const body = await res.json();