@zq-silk/yui 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +27 -9
  2. package/dist/cli/commandCatalog.js +7 -5
  3. package/dist/cli/invocationAuthority.js +4 -0
  4. package/dist/cli.js +130 -5
  5. package/dist/commands/releaseCommands.js +33 -24
  6. package/dist/commands/taskCommands.js +16 -25
  7. package/dist/commands/taskFactCommands.js +6 -2
  8. package/dist/controller/clientRuntime.js +7 -2
  9. package/dist/controller/domainIdentity.js +7 -7
  10. package/dist/controller/handoverCandidate.js +2 -5
  11. package/dist/controller/jobSupervisor.js +4 -4
  12. package/dist/controller/resourceCleanupLinux.js +35 -19
  13. package/dist/controller/resourceInventoryLinux.js +104 -7
  14. package/dist/controller/runtime.js +4 -13
  15. package/dist/controller/runtimeLaunchCoordinator.js +2 -4
  16. package/dist/controller/sessionOwnerReconciliation.js +40 -16
  17. package/dist/core/controllerClient.js +44 -16
  18. package/dist/core/controllerEndpoint.js +4 -3
  19. package/dist/core/controllerProcessIdentity.js +54 -0
  20. package/dist/core/controllerServer.js +16 -11
  21. package/dist/core/fileLockOwner.js +53 -12
  22. package/dist/doctor/doctor.js +230 -12
  23. package/dist/execution/workItemMainRun.js +1 -1
  24. package/dist/executor/agentAdapter.js +0 -8
  25. package/dist/executor/agentExecutor.js +4 -1
  26. package/dist/executor/fileRoleLaunchPlanner.js +3 -22
  27. package/dist/message/messageContinuation.js +1 -1
  28. package/dist/release/releaseHandover.js +77 -25
  29. package/dist/release/runtimeRelease.js +7 -7
  30. package/dist/repository/gitWorkspace.js +9 -8
  31. package/dist/repository/taskWorkspacePreparer.js +15 -57
  32. package/dist/resources/liveReferences.js +64 -7
  33. package/dist/runtime/agentEndpointIdentity.js +4 -1
  34. package/dist/runtime/agentHost.js +8 -9
  35. package/dist/runtime/codexInteractiveHost.js +1 -1
  36. package/dist/runtime/codexSharedDaemon.js +211 -0
  37. package/dist/runtime/index.js +1 -1
  38. package/dist/runtime/jsonLineChannel.js +6 -1
  39. package/dist/runtime/localStartup.js +65 -0
  40. package/dist/runtime/native/darwin-arm64/claude-process-owner +0 -0
  41. package/dist/runtime/native/darwin-arm64/process-identity +0 -0
  42. package/dist/runtime/native/darwin-x64/claude-process-owner +0 -0
  43. package/dist/runtime/native/darwin-x64/process-identity +0 -0
  44. package/dist/runtime/{claude-process-owner → native/linux-x64/claude-process-owner} +0 -0
  45. package/dist/runtime/nativeExecutable.js +10 -0
  46. package/dist/runtime/runtimeCoherence.js +10 -3
  47. package/dist/runtime/sessionOwnerIdentity.js +19 -0
  48. package/dist/runtime/sessionTerminationGuard.js +10 -3
  49. package/dist/runtime/structuredProviderHost.js +51 -26
  50. package/dist/scheduler/activeRoleRunDelivery.js +2 -11
  51. package/dist/scheduler/leaderWakeupProcessor.js +1 -2
  52. package/dist/scheduler/ports.js +1 -8
  53. package/dist/scheduler/taskExecutionProjection.js +1 -6
  54. package/dist/storage/sqliteSchema.js +15 -3
  55. package/dist/storage/storageVersions.js +1 -1
  56. package/dist/storage/taskCatalog.js +10 -2
  57. package/dist/storage/upgrade/taskMainWorkspaceMigration.js +83 -0
  58. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -6
  59. package/dist/task/task.js +0 -12
  60. package/dist/tmux/tmuxManager.js +8 -2
  61. package/dist/tmux/tmuxSocketEndpoint.js +23 -1
  62. package/dist/web/assets/client/app.js +131 -11
  63. package/dist/web/assets/client/i18n.js +18 -0
  64. package/dist/web/assets/client/taskSummary.js +7 -2
  65. package/dist/web/assets/client/taskSurface.js +54 -5
  66. package/dist/web/assets/client/view.js +7 -2
  67. package/dist/web/assets/shell.js +21 -11
  68. package/dist/web/assets/styles/layout.js +33 -6
  69. package/dist/web/assets/styles/responsive.js +18 -5
  70. package/dist/web/assets/styles/tokens.js +1 -1
  71. package/dist/web/assets/styles/widgets.js +5 -0
  72. package/docs/provider-runtime.md +7 -0
  73. package/docs/provider-runtime.zh-CN.md +5 -0
  74. package/docs/release-workflow.md +7 -1
  75. package/docs/task-delivery.md +5 -4
  76. package/docs/testing/verification-levels.md +9 -7
  77. package/i18n/README.zh-CN.md +19 -6
  78. package/package.json +5 -6
  79. package/skills/yui-leader/SKILL.md +8 -0
  80. package/skills/yui-leader/references/execution.md +4 -1
@@ -18,9 +18,10 @@ export function renderTaskSurface(container, data, t, locale, actions) {
18
18
  const summary = node("div", "section-body");
19
19
  summary.append(node("span", "detail-kicker", task.id), node("h2", "detail-title", task.title), pill(t, "status", task.status));
20
20
  renderTaskSummary(summary, data, t, locale, actions, recordCard);
21
- const discuss = node("details", "record-card");
21
+ const discuss = node(actions.conversationHost ? "section" : "details",
22
+ actions.conversationHost ? "conversation-compose record-card" : "record-card");
22
23
  discuss.dataset.viewKey = "discussion";
23
- discuss.append(node("summary", "", say("Discuss this Task", "讨论此任务")));
24
+ discuss.append(node(actions.conversationHost ? "h3" : "summary", "", say("Discuss this Task", "讨论此任务")));
24
25
  const conversation = node("button", "record-open", say("View Leader Session (read-only)", "查看 Leader Session(只读)"));
25
26
  conversation.type = "button";
26
27
  // A Draft's planning Session is a real Leader Session, so it is viewable once it
@@ -96,7 +97,43 @@ export function renderTaskSurface(container, data, t, locale, actions) {
96
97
  }
97
98
  });
98
99
  discuss.append(chat);
99
- summary.append(discuss);
100
+ if (actions.conversationHost) {
101
+ const feed = node("div", "conversation-feed");
102
+ feed.setAttribute("aria-label", say("Recorded Task messages", "已记录的 Task 消息"));
103
+ const messages = records("task-message");
104
+ messages.forEach((entry) => {
105
+ if (entry.omitted) {
106
+ feed.append(recordCard(entry));
107
+ return;
108
+ }
109
+ const value = entry.value;
110
+ const item = node("article", "conversation-item");
111
+ if (value.author?.type === "user") item.classList.add("is-user");
112
+ const head = node("div", "conversation-item-head");
113
+ const author = value.author?.roleName || t("author." + (value.author?.type || "system"));
114
+ head.append(node("strong", "", author));
115
+ if (value.createdAt) {
116
+ const when = node("time", "", new Intl.DateTimeFormat(locale, {
117
+ month: "short", day: "numeric", hour: "2-digit", minute: "2-digit"
118
+ }).format(new Date(value.createdAt)));
119
+ when.dateTime = value.createdAt;
120
+ head.append(when);
121
+ }
122
+ item.append(head, richText(null, value.body || "", t));
123
+ feed.append(item);
124
+ });
125
+ if (!messages.length) feed.append(node("p", "muted", core.omitted.records
126
+ ? say("Compact read omits some messages. Open History & messages for source references.",
127
+ "紧凑读取省略了部分消息;可在「历史与消息」查看来源引用。")
128
+ : say("No recorded Task messages yet.", "尚无已记录的 Task 消息。")));
129
+ if (core.omitted.records && messages.length) feed.append(node("p", "muted",
130
+ say("Compact read may omit older messages; see History & messages for source references.",
131
+ "紧凑读取可能省略较早消息;可在「历史与消息」查看来源引用。")));
132
+ actions.conversationHost.replaceChildren(feed, discuss);
133
+ actions.conversationHost.dataset.taskId = task.id;
134
+ } else {
135
+ summary.append(discuss);
136
+ }
100
137
  // decision-3 three-action input control (queue / steer / interrupt). This is
101
138
  // the same application-layer path the CLI drives; the Web surface adds no
102
139
  // fourth action and no auto-fallback. Steer/interrupt name an exact current
@@ -378,10 +415,14 @@ export function renderTaskSurface(container, data, t, locale, actions) {
378
415
  scaffold.append(anchorSection(id, null, folded));
379
416
  return body;
380
417
  }
381
- const focus = section("detail-focus", say("Goal and current focus", "目标与当前关注"), records("task-brief"));
418
+ const working = task.status === "active" || task.status === "draft";
419
+ const focus = section("detail-focus", working
420
+ ? say("Goal and current focus", "目标与当前关注")
421
+ : say("Goal and last working focus", "目标与结束前的工作重点"), records("task-brief"));
382
422
  const brief = values("task-brief")[0];
383
423
  if (brief) {
384
- focus.prepend(richText(t("detail.focus"), brief.currentFocus, t));
424
+ focus.prepend(richText(working
425
+ ? t("detail.focus") : say("Last working focus", "结束前的工作重点"), brief.currentFocus, t));
385
426
  if (brief.boundaries.length) focus.append(richText(say("Boundaries", "边界"), brief.boundaries.join("\\n"), t));
386
427
  }
387
428
  section("detail-work", say("Responsibilities and acceptance", "工作责任与验收"), records("work-item"));
@@ -430,6 +471,14 @@ export function renderTaskSurface(container, data, t, locale, actions) {
430
471
  execution.append(raw);
431
472
  scaffold.append(anchorSection("detail-exec", sectionHead(t("detail.execution")), execution));
432
473
  section("detail-operations", say("Original operation facts (no automatic retry)", "原始操作事实(不自动重试)"), records("job"));
474
+ // Four reading groups; the source sections keep their stable deep-link IDs.
475
+ const readingOrder = ["detail-top", "detail-focus", "detail-work", "detail-results",
476
+ "detail-exec", "detail-reviews", "detail-roles", "detail-history",
477
+ "detail-messages", "detail-operations"];
478
+ readingOrder.forEach((id) => {
479
+ const target = scaffold.querySelector("#" + id);
480
+ if (target) scaffold.append(target);
481
+ });
433
482
  const refs = node("details", "record-card");
434
483
  refs.append(node("summary", "", say("Context cursor and omitted references", "Context 游标与省略引用")));
435
484
  refs.append(node("pre", "surface-json", JSON.stringify({
@@ -478,11 +478,16 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
478
478
  const focusBody = node("div", "section-body");
479
479
  if (data.brief) {
480
480
  const focusCard = node("article", "record-card");
481
- focusCard.append(richText(t("detail.focus"), data.brief.currentFocus || data.brief.objective, t));
481
+ const working = task.status === "active" || task.status === "draft";
482
+ focusCard.append(richText(working
483
+ ? t("detail.focus") : (locale.startsWith("zh") ? "结束前的工作重点" : "Last working focus"),
484
+ data.brief.currentFocus || data.brief.objective, t));
482
485
  if (data.brief.technicalApproach) {
483
486
  focusCard.append(richText(t("detail.technicalApproach"), data.brief.technicalApproach, t, { muted: true }));
484
487
  }
485
- if (data.brief.leaderSummary) focusCard.append(richText(t("detail.leaderSummary"), data.brief.leaderSummary, t, { muted: true }));
488
+ if (data.brief.leaderSummary) focusCard.append(richText(working
489
+ ? t("detail.leaderSummary") : (locale.startsWith("zh") ? "结束前的进展摘要" : "Last working report"),
490
+ data.brief.leaderSummary, t, { muted: true }));
486
491
  focusBody.append(focusCard);
487
492
  } else {
488
493
  focusBody.append(emptyRow(t, "empty.brief"));
@@ -75,30 +75,40 @@ export const DASHBOARD_HTML = `<!doctype html>
75
75
  </div>
76
76
  </div>
77
77
  <div class="topbar-actions">
78
+ <button id="conversation-toggle" class="operator-open" type="button" aria-controls="conversation-panel" aria-expanded="false" data-i18n="conversation.show" hidden>Task discussion</button>
78
79
  <div class="clock">
79
80
  <span data-i18n="sync.label">Last sync</span>
80
81
  <time id="last-sync">—</time>
81
82
  </div>
82
- <button id="operator-terminal" class="operator-open" type="button">
83
+ <button id="operator-terminal" class="operator-open" type="button" aria-label="Operator session" data-i18n-aria-label="actions.operator">
83
84
  <span class="operator-title" data-i18n="actions.operator">Operator session</span>
84
85
  <span class="operator-shortcuts" aria-hidden="true"><kbd>O</kbd></span>
85
86
  </button>
86
- <button id="refresh" class="refresh" type="button" title="Refresh tasks"><span data-i18n="actions.refresh">Refresh</span> <kbd>R</kbd></button>
87
+ <button id="refresh" class="refresh" type="button" title="Refresh tasks" aria-label="Refresh" data-i18n-aria-label="actions.refresh"><span data-i18n="actions.refresh">Refresh</span> <kbd>R</kbd></button>
87
88
  </div>
88
89
  </header>
89
90
  <nav id="detail-tabs" class="detail-tabs" aria-label="Task sections" data-i18n-aria-label="tabs.label" hidden>
90
- <button class="tab" type="button" data-target="detail-top" data-i18n="tabs.summary">Summary</button>
91
- <button class="tab" type="button" data-target="detail-focus" data-i18n="tabs.focus">Focus</button>
92
- <button class="tab" type="button" data-target="detail-results" data-i18n="tabs.results">Results</button>
93
- <button class="tab" type="button" data-target="detail-work" data-i18n="tabs.work">Work items</button>
94
- <button class="tab" type="button" data-target="detail-exec" data-i18n="tabs.exec">AgentRuns</button>
95
- <button class="tab" type="button" data-target="detail-reviews" data-i18n="tabs.reviews">Reviews</button>
96
- <button class="tab" type="button" data-target="detail-roles" data-i18n="tabs.roles">Roles</button>
97
- <button class="tab" type="button" data-target="detail-history" data-i18n="tabs.history">History</button>
98
- <button class="tab" type="button" data-target="detail-messages" data-i18n="tabs.messages">Messages</button>
91
+ <button class="tab" type="button" data-target="detail-top" data-i18n="tabs.summary">Overview</button>
92
+ <button class="tab" type="button" data-target="detail-work" data-i18n="tabs.workResults">Work &amp; results</button>
93
+ <button class="tab" type="button" data-target="detail-exec" data-i18n="tabs.executionReviews">Execution &amp; reviews</button>
94
+ <button class="tab" type="button" data-target="detail-history" data-i18n="tabs.historyMessages">History &amp; messages</button>
99
95
  </nav>
100
96
  <main id="detail" class="detail" aria-labelledby="page-title" tabindex="-1"></main>
101
97
  </div>
98
+ <div id="pane-divider" class="pane-divider" role="separator" tabindex="0" aria-label="Resize task discussion" aria-orientation="vertical" aria-valuemin="280" aria-valuemax="640" aria-valuenow="400" data-i18n-aria-label="conversation.resize" hidden><span aria-hidden="true"></span></div>
99
+ <aside id="conversation-panel" class="conversation-panel" aria-labelledby="conversation-title" hidden>
100
+ <header class="conversation-head">
101
+ <div>
102
+ <span class="conversation-eyebrow" data-i18n="conversation.source">Recorded Task messages · not a Session transcript</span>
103
+ <h2 id="conversation-title" data-i18n="conversation.title">Task discussion</h2>
104
+ </div>
105
+ <div class="conversation-head-actions">
106
+ <button id="conversation-swap" class="pane-action" type="button" title="Move discussion to the other side" data-i18n-aria-label="conversation.swap" aria-label="Move discussion to the other side">⇄</button>
107
+ <button id="conversation-close" class="pane-action" type="button" title="Hide discussion" data-i18n-aria-label="conversation.hide" aria-label="Hide discussion">×</button>
108
+ </div>
109
+ </header>
110
+ <div id="conversation-content" class="conversation-content task-surface"></div>
111
+ </aside>
102
112
  <dialog id="global-input-dialog" class="global-input-dialog" aria-labelledby="global-input-title">
103
113
  <h2 id="global-input-title">Global Role input · 全局 Role 消息</h2>
104
114
  <p class="muted">Queue is the default. A cancel request does not prove the Turn stopped.<br>默认排队;取消请求不代表执行已停止。</p>
@@ -6,10 +6,13 @@ export const LAYOUT_STYLES = `
6
6
  *{box-sizing:border-box}
7
7
  html{min-width:320px;font-family:var(--font-body)}
8
8
  body{margin:0;min-width:320px;height:100vh;overflow:hidden;font-size:14px}
9
- .app-shell{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr) 0;grid-template-rows:100vh;height:100vh;transition:grid-template-columns var(--motion-slow) var(--ease)}
10
- body.terminal-active .app-shell{grid-template-columns:var(--sidebar-w) minmax(0,1fr) var(--terminal-w)}
9
+ .app-shell{display:grid;grid-template-areas:"sidebar main divider pane";grid-template-columns:var(--sidebar-w) minmax(0,1fr) 0 0;grid-template-rows:100vh;height:100vh;transition:grid-template-columns var(--motion-slow) var(--ease)}
10
+ body.conversation-active .app-shell{grid-template-columns:var(--sidebar-w) minmax(0,1fr) 8px var(--pane-w)}
11
+ body.conversation-active.pane-left .app-shell{grid-template-areas:"sidebar pane divider main";grid-template-columns:var(--sidebar-w) var(--pane-w) 8px minmax(0,1fr)}
12
+ body.terminal-active .app-shell,body.terminal-active.pane-left .app-shell{grid-template-areas:"sidebar main divider pane";grid-template-columns:var(--sidebar-w) minmax(0,1fr) 0 var(--terminal-w)}
13
+ body.pane-resizing .app-shell{transition:none}
11
14
  /* Sidebar = persistent work index */
12
- .sidebar{display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;padding:16px 14px 14px}
15
+ .sidebar{grid-area:sidebar;display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;padding:16px 14px 14px}
13
16
  .sidebar-brand{display:flex;gap:10px;align-items:center;flex:none;margin-bottom:14px}
14
17
  .brand-text{display:grid;gap:3px;min-width:0;flex:1}
15
18
  .sidebar .search{flex:none;margin-bottom:10px}
@@ -27,7 +30,7 @@ body.terminal-active .app-shell{grid-template-columns:var(--sidebar-w) minmax(0,
27
30
  .catalog-pager button:disabled{opacity:.45;cursor:default}
28
31
  #catalog-attention-reset{grid-column:1/-1}
29
32
  /* Main column = top bar + optional section nav + reading surface */
30
- .main-col{display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;overflow-y:auto;scrollbar-gutter:stable}
33
+ .main-col{grid-area:main;display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;overflow-y:auto;scrollbar-gutter:stable}
31
34
  .topbar{position:sticky;top:0;z-index:30;flex:none;display:flex;align-items:center;justify-content:space-between;gap:18px;padding:11px max(var(--page-space),calc((100% - 1100px) / 2));background:color-mix(in srgb,var(--bg) 92%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--border)}
32
35
  .topbar-leading{display:flex;align-items:center;gap:14px;min-width:0}
33
36
  .breadcrumb{display:flex;align-items:baseline;gap:8px;min-width:0}
@@ -43,8 +46,32 @@ body.detail-active .detail-tabs{display:flex}
43
46
  .detail{flex:1;min-height:0;padding:16px max(var(--page-space),calc((100% - 1100px) / 2)) 72px;min-width:0}
44
47
  .detail:focus-visible{outline:none}
45
48
  .anchor{scroll-margin-top:calc(var(--topbar-h,44px) + var(--tabs-h,38px))}
46
- /* Terminal panel */
47
- .terminal-panel{grid-column:3;min-width:0;height:100vh;display:flex;flex-direction:column;overflow:hidden}
49
+ /* Adjustable discussion panel; the separator belongs to the task view, not Task state. */
50
+ .pane-divider{grid-area:divider;align-self:stretch;width:8px;min-width:0;cursor:col-resize;position:relative;touch-action:none;z-index:31}
51
+ .pane-divider[hidden]{display:none}
52
+ .pane-divider span{position:absolute;inset:0 3px;background:var(--border-strong);transition:background var(--motion-fast)}
53
+ .pane-divider:hover span,.pane-divider:focus-visible span,body.pane-resizing .pane-divider span{background:var(--accent)}
54
+ .pane-divider:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
55
+ .conversation-panel{grid-area:pane;min-width:0;height:100vh;min-height:0;display:flex;flex-direction:column;overflow:hidden}
56
+ .conversation-panel[hidden]{display:none}
57
+ .conversation-head{flex:none;min-height:72px;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 16px}
58
+ .conversation-head>div:first-child{min-width:0}
59
+ .conversation-eyebrow{display:block;font-size:10px;line-height:1.4}
60
+ .conversation-head h2{margin:4px 0 0;font-size:16px;font-family:var(--font-display);line-height:1.25}
61
+ .conversation-head-actions{display:flex;gap:4px;align-items:center}
62
+ .conversation-content{display:flex;flex:1;flex-direction:column;min-height:0;padding:14px;gap:14px}
63
+ .conversation-feed{display:flex;flex:1;flex-direction:column;gap:10px;min-height:0;overflow-y:auto;scrollbar-gutter:stable;padding:2px 2px 12px}
64
+ .conversation-item{flex:none;padding:11px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-2);min-width:0;overflow-wrap:anywhere}
65
+ .conversation-item.is-user{border-color:var(--accent-line);background:var(--accent-soft)}
66
+ .conversation-item-head{display:flex;justify-content:space-between;gap:10px;color:var(--muted);font-size:11px;margin-bottom:8px}
67
+ .conversation-item-head time{white-space:nowrap}
68
+ .conversation-item .rich-text{font-size:13px}
69
+ .conversation-compose{flex:none;max-height:55%;overflow-y:auto}
70
+ .conversation-compose h3{margin:2px 0;font-size:13px}
71
+ .conversation-compose form{display:grid;gap:9px}
72
+ .conversation-compose textarea{min-height:76px}
73
+ /* Native Session occupies the pane temporarily; it is not the Task message feed. */
74
+ .terminal-panel{grid-area:pane;min-width:0;height:100vh;display:flex;flex-direction:column;overflow:hidden}
48
75
  .terminal-panel[hidden]{display:none}
49
76
  .terminal-head{flex:none;height:62px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:10px 14px}
50
77
  .terminal-head>div{display:flex;align-items:center;gap:12px;min-width:0}
@@ -11,8 +11,7 @@ export const RESPONSIVE_STYLES = `
11
11
  @media(max-width:900px){
12
12
  /* Master-detail: sidebar list by default; selecting a task swaps to detail */
13
13
  body{overflow:auto}
14
- .app-shell{grid-template-columns:1fr;height:auto;min-height:100vh}
15
- body.terminal-active .app-shell{grid-template-columns:1fr}
14
+ .app-shell,body.conversation-active .app-shell,body.conversation-active.pane-left .app-shell,body.terminal-active .app-shell,body.terminal-active.pane-left .app-shell{grid-template-areas:"sidebar" "main";grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto;height:auto;min-height:100vh}
16
15
  .sidebar{height:auto;min-height:100vh;border-right:0;border-bottom:1px solid var(--border)}
17
16
  .task-list{overflow:visible}
18
17
  .main-col{display:none;height:auto;min-height:100vh}
@@ -20,10 +19,16 @@ export const RESPONSIVE_STYLES = `
20
19
  body.detail-active .main-col{display:flex}
21
20
  body.detail-active .detail-back{display:grid}
22
21
  .detail{overflow:visible}
22
+ .pane-divider{display:none}
23
+ .conversation-panel{position:fixed;inset:0;z-index:70;height:100dvh;border-left:0}
24
+ .conversation-feed{min-height:0}
25
+ .conversation-head-actions #conversation-swap{display:none}
26
+ body.conversation-active .main-col{display:none}
27
+ body.conversation-active .conversation-panel{display:flex}
23
28
  .overview-duo{grid-template-columns:1fr}
24
29
  .observability-metrics{grid-template-columns:repeat(3,minmax(0,1fr))}
25
30
  /* Terminal panel overlays full-screen instead of occupying a grid column */
26
- .terminal-panel{position:fixed;inset:0;z-index:70;grid-column:auto;height:100vh;border-left:0}
31
+ .terminal-panel{position:fixed;inset:0;z-index:80;height:100dvh;border-left:0}
27
32
  }
28
33
  @media(max-width:620px){
29
34
  .command-rail{grid-template-columns:repeat(2,1fr)}
@@ -31,8 +36,16 @@ export const RESPONSIVE_STYLES = `
31
36
  .metric-value{font-size:26px}
32
37
  /* Keep the topbar on one line: clock and key hints are expendable */
33
38
  .clock{display:none}
34
- .refresh kbd{display:none}
35
- .topbar{flex-wrap:nowrap;gap:10px}
39
+ .topbar{flex-wrap:nowrap;gap:8px;padding:10px 14px}
40
+ .topbar-leading{flex:1;gap:8px}
41
+ .breadcrumb .crumb,.breadcrumb .crumb-sep{display:none}
42
+ .topbar-actions{gap:6px}
43
+ #conversation-toggle,#operator-terminal,#refresh{width:38px;height:38px;min-width:38px;justify-content:center;padding:0}
44
+ #conversation-toggle{font-size:0}
45
+ #conversation-toggle::before{content:"▤";font-size:19px}
46
+ #operator-terminal .operator-title,#operator-terminal .operator-shortcuts,#refresh span,#refresh kbd{display:none}
47
+ #operator-terminal::before{content:">_";font-family:var(--font-mono);font-size:15px}
48
+ #refresh::before{content:"↻";font-size:20px}
36
49
  .detail-tabs{mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent)}
37
50
  .record-cols{grid-template-columns:1fr}
38
51
  .observability-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
@@ -35,7 +35,7 @@ export const TOKEN_STYLES = `
35
35
  --font-mono:"JetBrains Mono","SFMono-Regular",Consolas,monospace;
36
36
  --font-display:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;
37
37
  --font-body:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;
38
- --page-space:clamp(18px,2.6vw,40px);--sidebar-w:clamp(232px,18vw,288px);--terminal-w:clamp(420px,34vw,640px);
38
+ --page-space:clamp(18px,2.6vw,40px);--sidebar-w:clamp(232px,18vw,288px);--terminal-w:clamp(420px,34vw,640px);--pane-w:clamp(300px,29vw,500px);
39
39
  --motion-fast:150ms;--motion-slow:320ms;--ease:cubic-bezier(.16,.84,.44,1);
40
40
  }
41
41
  :root,[data-theme="control-room"]{
@@ -142,6 +142,11 @@ kbd{background:var(--bg-3);border:1px solid var(--border);border-bottom-width:2p
142
142
  .metric.is-warning .metric-value{color:var(--warning)}
143
143
  /* Terminal panel */
144
144
  .terminal-panel{color:#e8eef6;background:#080b11;border-left:1px solid #263244;box-shadow:var(--shadow-pop)}
145
+ .conversation-panel{color:var(--text);background:var(--bg-1);border-left:1px solid var(--border);box-shadow:var(--shadow-card)}
146
+ .conversation-head{border-bottom:1px solid var(--border);background:var(--bg-2)}
147
+ .conversation-eyebrow{color:var(--muted)}
148
+ .pane-action{display:grid;place-items:center;width:34px;height:34px;padding:0;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-1);color:var(--text);font:inherit;font-size:20px;cursor:pointer}
149
+ .pane-action:hover,.pane-action:focus-visible{border-color:var(--accent);color:var(--accent)}
145
150
  .terminal-head{background:#0c111a;border-bottom:1px solid #263244}
146
151
  .terminal-head h2{font-family:var(--font-display);font-size:15px;color:#e8eef6}
147
152
  .terminal-close{display:grid!important;color:#8b99ad;background:#111826;border-color:#263244}
@@ -143,6 +143,13 @@ Failure to drain remains a live ownership dependency, not false quiescence.
143
143
  This is OS process custody, not a Task workflow or a claim about unrelated
144
144
  external services.
145
145
 
146
+ macOS has no child subreaper. The owner stops descendants it can still prove
147
+ through ancestry or the original process group. If the Claude root forks,
148
+ `setsid` and reparenting can hide a child from both checks; the owner therefore
149
+ reports cleanup as unconfirmed instead of returning a successful exit. This
150
+ also covers ordinary forked tools whose exit cannot be proven after the root
151
+ is gone. A detached child may still need explicit inspection and termination.
152
+
146
153
  The dedicated process's PID/start identity is retained as engineering control
147
154
  data independently of AgentHost. Recovery never needs the old Host's in-memory
148
155
  connection. For Codex, a separate metadata/control client can inspect and stop
@@ -117,6 +117,11 @@ Task Session 终止首先取消其确切的未结算输入,并在向 Host 发
117
117
  一个活的所有权依赖,而不是虚假静止。这是 OS 进程托管,不是 Task 工作流,也不是关于
118
118
  无关外部服务的断言。
119
119
 
120
+ macOS 没有子进程 subreaper。owner 会停止仍能通过父子关系或原进程组确认的后代。
121
+ 若 Claude 根进程曾 fork,子进程再执行 `setsid` 并被重新托管,就可能避开这两种检查;
122
+ 此时 owner 会报告清理未确认,而不会返回成功;普通工具进程曾 fork、但根进程退出后
123
+ 无法证明它们都已结束时,也适用这一保守结果。脱离的子进程可能仍需单独检查和终止。
124
+
120
125
  专用进程的 PID/start 身份作为工程控制数据独立于 AgentHost 保留。恢复从不需要旧 Host
121
126
  的内存连接。对 Codex,一个单独的元数据/控制客户端可以在不提交另一个 prompt 的情况下
122
127
  检查并停止已记录的原生执行;未知接受可以在实际原生静止之后被放弃,而不编造原生 Turn
@@ -45,10 +45,16 @@ protection and valid exact-identity caches remain normal runtime behavior.
45
45
 
46
46
  Stable releases use npm `latest`; prereleases use `next`, never the stable
47
47
  default. The release workflow builds one runtime archive, verifies it across
48
- supported Node versions, publishes that exact archive to npm, then creates the
48
+ supported Node versions on Linux x64, macOS x64 and macOS arm64, publishes that exact archive to npm, then creates the
49
49
  matching GitHub Release. Every release carries the runtime archive, checksum and
50
50
  provenance. A stable tag becomes GitHub latest; a prerelease does not.
51
51
 
52
+ Platform jobs compile Yui's native helpers from the same source commit.
53
+ The archive includes every supported helper under `dist/runtime/native/`;
54
+ runtime selection uses the exact OS and architecture, without install scripts
55
+ or downloading executable code on first use. Dependencies such as SQLite
56
+ remain ordinary npm dependencies installed for the consumer's platform.
57
+
52
58
  The GitHub Release job uploads only missing assets to a draft, downloads and
53
59
  compares every asset against the tested artifact, and never overwrites a
54
60
  published asset. A retry accepts only byte-identical existing evidence. If npm
@@ -35,9 +35,10 @@ isolation and acceptance checks remain enforced independently.
35
35
 
36
36
  Stable Project checkouts are read-only references. Task main is a logical
37
37
  multi-Project root with independent per-Project Git clones; WorkItem, Review and
38
- Integration worktrees belong to those Task repositories. For one Project, the Agent's
39
- normal cwd is its managed Git root; for multiple Projects, the root and native
40
- additional-directory mechanism expose the explicit Project set.
38
+ Integration worktrees belong to those Task repositories. By default, a Task's native cwd is
39
+ its managed root with zero, one, or multiple Projects. The native
40
+ additional-directory mechanism exposes each bound Project. Binding another
41
+ Project while the Task is active does not move the Task cwd.
41
42
 
42
43
  An isolated WorkItem has independent worktrees for writable Projects and
43
44
  Task-main context for the others. Write scope is explicit and can only be
@@ -367,7 +368,7 @@ This adds neither a retry worker nor another persistent ownership protocol.
367
368
  Current Resource records are read strictly: required safety fields, enum values,
368
369
  and every active reference must be valid. A malformed record or mismatched
369
370
  SQLite/payload identity is reported without supplying defaults, dropping refs,
370
- or rewriting stored evidence. This enforces the current storage 1.0 record contract.
371
+ or rewriting stored evidence. This enforces the current storage 1.1 record contract.
371
372
 
372
373
  Failed preparation compensates only its unadopted resources. Standalone Task
373
374
  clones use exact clone identity/cleanliness checks before direct deletion;
@@ -50,13 +50,12 @@ is required.
50
50
  ## Permanent core smoke
51
51
 
52
52
  `npm test` and `npm run test:core` build the checkout and run the maintained suite.
53
- The stable suite owns only current storage 1.0 and future declared minor
54
- transitions. It contains no historical conversion implementation, fixture or
55
- compatibility test.
53
+ The stable suite owns current storage 1.1 and its declared 1.0 minor
54
+ transition. It contains no undeclared historical format compatibility path.
56
55
 
57
56
  Current coverage includes:
58
57
 
59
- 1. Fresh storage 1.0, exact schema/record validation, no initialization over
58
+ 1. Fresh storage 1.1, exact schema/record validation, no initialization over
60
59
  unknown data, and rejection of old integer formats without mutation.
61
60
  2. Exact-version staging, mismatched-target refusal, same-major contiguous
62
61
  minor preflight, explicit maintenance-owner identity across handover, and
@@ -99,8 +98,9 @@ do not add prose-matching tests or claim model validation from static checks.
99
98
 
100
99
  ## CI and release
101
100
 
102
- `ci.yml` builds once and runs core plus one assembled-package normal-path smoke
103
- on every PR, without another lint or broad regression suite.
101
+ `ci.yml` runs core plus one assembled-package normal-path smoke per supported
102
+ platform (Linux x64, Mac Intel and Apple Silicon) on every PR, without another
103
+ lint or broad regression suite.
104
104
  `node scripts/smoke-runtime-package.mjs --assembled .release-stage` exercises
105
105
  the actual CLI/Controller/Host/SQLite and isolated tmux, replacing only the
106
106
  external Provider with a deterministic fixture. It covers setup, durable input
@@ -111,7 +111,9 @@ and grouped viewers without affecting a similarly named neighboring session.
111
111
  The fixture owns a fresh Home and its PATH, installs cleanup before setup,
112
112
  and never calls an installed model Agent.
113
113
 
114
- `publish.yml` runs the same smoke against the freshly installed package through
114
+ `publish.yml` builds one archive containing all three native targets and runs
115
+ the same smoke on each platform and supported Node version against that exact
116
+ freshly installed package through
115
117
  `YUI_INSTALLED_ROOT`, adding actual npm-bin, dependency, supported Node version,
116
118
  artifact and provenance boundaries. This validates runtime integration, not
117
119
  real-model behavior. Pure contract and safety tests remain in `test/core`;
@@ -5,7 +5,7 @@
5
5
  [![Core CI](https://github.com/zhangqian-silk/yui/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/zhangqian-silk/yui/actions/workflows/ci.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../LICENSE)
7
7
  ![Node](https://img.shields.io/badge/node-20%20%7C%2022%20%7C%2024-brightgreen.svg)
8
- ![Platform](https://img.shields.io/badge/platform-Linux%20x64%20%28glibc%29-blue.svg)
8
+ ![Platform](https://img.shields.io/badge/platform-Linux%20x64%20%28glibc%29%20%7C%20macOS%20x64%2Farm64-blue.svg)
9
9
  [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](#参与开发)
10
10
 
11
11
  让 Agent 持续推进你的任务,而不只是回答一轮对话。
@@ -30,16 +30,21 @@ Yui 是面向编程 Agent 的本地控制面。你只需用自然语言把目标
30
30
  Web 仅本地回环,包含只读视图和经认证的用户控制。
31
31
  - **默认隔离** —— 仓库改动发生在受管 Git worktree 中,稳定 checkout 保持只读。
32
32
 
33
- > **状态:** 1.0.0,采用纯净的存储 1.0 基线。默认更新只执行同一存储
33
+ > **状态:** 1.1.0,当前存储版本为 1.1,已声明从 1.0 基线升级。默认更新只执行同一存储
34
34
  > 主版本内已声明的小版本升级;其他存储身份会在不修改 Home 的前提下被拒绝。
35
35
 
36
36
  [快速开始](#快速开始) · [通过对话管理工作](#通过对话管理工作) · [架构](#架构) · [设计原则](#设计原则)
37
37
 
38
38
  ## 快速开始
39
39
 
40
- 需要 Linux x64 / glibc、Git、tmux,以及 Node.js `^20.17.0`、`^22.9.0` 或
41
- `^24.0.0`。最简单的方式是先安装 Codex CLI 或 Claude Code CLI,并确保它
42
- 已经可以使用你自己的账号正常工作。Yui 负责协调 Agent,不提供模型访问额度。
40
+ npm 包包含 Linux x64、Mac Intel 和 Apple Silicon 的 Yui 预编译程序。
41
+ 同一份包在三个平台验证后发布,运行时只选择对应平台的程序。
42
+
43
+ 需要 Linux x64 / glibc 或 macOS(x64 或 Apple Silicon)、Git、tmux,以及
44
+ Node.js `^20.17.0`、`^22.9.0` 或 `^24.0.0`。macOS 上可用
45
+ `brew install tmux` 安装 tmux。最简单的方式是先安装 Codex CLI 或 Claude
46
+ Code CLI,并确保它已经可以使用你自己的账号正常工作。Yui 负责协调 Agent,
47
+ 不提供模型访问额度。
43
48
 
44
49
  Yui 传递 Claude 的认证环境并保留原生配置目录,由 Claude 自身按本地配置
45
50
  选择 API key 或登录方式。更换 Session 不会重置登录或初始化记录。首次运行时,
@@ -64,10 +69,18 @@ yui setup
64
69
  > 我已经安装了 Yui。请在交互式终端中帮我执行 `yui setup`,选择可用的
65
70
  > Agent,并用 `yui doctor` 检查结果。遇到账号或需要我决定的配置时问我。
66
71
 
67
- Setup 会配置与你对话的 Operator 和默认任务 Leader,并启动本地 Controller。
72
+ Setup 会配置与你对话的 Operator 和默认任务 Leader,并启动本地 Controller,
73
+ 以及这些活跃 Role 需要的 Codex 共享 daemon。
68
74
  先用这两个角色即可开始,Worker、Reviewer 可以之后再配置。如果 Agent
69
75
  没有操作交互式终端的能力,就自己运行 setup;它只负责初始配置。
70
76
 
77
+ 之后启动时运行 `yui start`。它会按需启动 Controller;只有活跃 Role 使用
78
+ Codex 时才会启动 Codex 共享 daemon。在 macOS 上,如果安装了 Codex App,
79
+ 它还会配置 App 下次启动时连接该 daemon。先运行 `yui start` 再打开 App;
80
+ 如果 App 已经打开,按 Yui 的提示退出并重新打开。`yui doctor` 可以检查
81
+ 实时连接。`yui controller start` 只启动 Controller。Linux 不需要 App 配置;
82
+ 没有活跃 Codex Role 时不会启动 Codex daemon。
83
+
71
84
  ### 3. 开始对话
72
85
 
73
86
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -22,13 +22,12 @@
22
22
  "node": "^20.17.0 || ^22.9.0 || ^24.0.0"
23
23
  },
24
24
  "os": [
25
- "linux"
25
+ "linux",
26
+ "darwin"
26
27
  ],
27
28
  "cpu": [
28
- "x64"
29
- ],
30
- "libc": [
31
- "glibc"
29
+ "x64",
30
+ "arm64"
32
31
  ],
33
32
  "keywords": [
34
33
  "yui",
@@ -69,6 +69,14 @@ The DB Brief is the current summary, not a full plan or a history of messages.
69
69
  After meaningful progress, use `task brief update` with only intended fields;
70
70
  read `task event list` before deliberately restoring an older value. Keep
71
71
  references to substantial plans, prototypes and reports in that summary.
72
+ When a delivered correction resolves a recorded blocker or changes the next
73
+ action, update the Brief's focus and Leader summary while the Task is still
74
+ open. Before completing a Task, reread its Brief: if it still describes a
75
+ resolved blocker, superseded plan, or waiting state, correct those fields
76
+ before `task complete`. The completion summary is the terminal conclusion;
77
+ do not copy it into Brief merely to create a second completion record. Brief
78
+ maintenance is not a new completion gate, and routine unchanged steps do not
79
+ need an update.
72
80
  Decisions contain the actual decision, reason and necessary boundaries, not
73
81
  the entire proposal. Label recommendations as recommendations, not user
74
82
  decisions.
@@ -409,7 +409,10 @@ Before ending authorized execution:
409
409
  with `yui task run show`, read each original result in full, and make the
410
410
  next decision.
411
411
  2. Persist actual WorkItem lifecycle and material Brief, Decision, Milestone,
412
- Message, or Knowledge changes.
412
+ Message, or Knowledge changes. If an accepted result or authorized
413
+ correction has cleared a blocker still stated as current in Brief, update
414
+ that working summary before completing or yielding. Keep the original
415
+ failure and correction in their durable records.
413
416
  3. Choose one truthful outcome: continue through an owned native child, complete
414
417
  the Task, create a justified InputRequest, or leave the active Task waiting
415
418
  for a real durable event.