pi-ui-extend 1.0.23 → 1.0.28

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 (44) hide show
  1. package/README.md +5 -1
  2. package/dist/app/app.d.ts +1 -0
  3. package/dist/app/app.js +12 -0
  4. package/dist/app/constants.d.ts +1 -1
  5. package/dist/app/constants.js +1 -1
  6. package/dist/app/icons.d.ts +1 -0
  7. package/dist/app/icons.js +2 -0
  8. package/dist/app/input/input-controller.d.ts +2 -0
  9. package/dist/app/input/input-controller.js +8 -5
  10. package/dist/app/rendering/render-controller.js +2 -0
  11. package/dist/app/rendering/status-line-renderer.d.ts +4 -1
  12. package/dist/app/rendering/status-line-renderer.js +12 -0
  13. package/dist/app/runtime.d.ts +1 -8
  14. package/dist/app/runtime.js +0 -39
  15. package/dist/app/screen/mouse-controller.d.ts +4 -1
  16. package/dist/app/screen/mouse-controller.js +9 -0
  17. package/dist/app/session/agent-pause-controller.d.ts +30 -0
  18. package/dist/app/session/agent-pause-controller.js +178 -0
  19. package/dist/app/session/session-event-controller.d.ts +1 -0
  20. package/dist/app/session/session-event-controller.js +22 -15
  21. package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
  22. package/dist/app/session/session-lifecycle-controller.js +1 -0
  23. package/dist/app/types.d.ts +10 -0
  24. package/dist/config.js +1 -1
  25. package/dist/default-pix-config.js +2 -2
  26. package/external/pi-tools-suite/README.md +1 -1
  27. package/external/pi-tools-suite/docs/browser-qa-subagent.md +34 -2
  28. package/external/pi-tools-suite/package.json +3 -3
  29. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +24 -18
  30. package/external/pi-tools-suite/src/async-subagents/core/config.ts +14 -3
  31. package/external/pi-tools-suite/src/async-subagents/core/process.ts +19 -0
  32. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +97 -17
  33. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +4 -2
  34. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +108 -13
  35. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +80 -0
  36. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-flow.example.jsonc +54 -1
  37. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +952 -76
  38. package/external/pi-tools-suite/src/coding-discipline/index.ts +2 -94
  39. package/external/pi-tools-suite/src/dcp/index.ts +21 -0
  40. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +92 -0
  41. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +21 -16
  42. package/external/pi-tools-suite/src/todo/index.ts +86 -46
  43. package/external/pi-tools-suite/src/todo/todo.ts +1 -1
  44. package/package.json +4 -4
@@ -134,7 +134,8 @@ export class AppSessionEventController {
134
134
  this.historyEntries = [];
135
135
  this.historyWindowStart = 0;
136
136
  const lazyOlderHistory = options.lazyOlderHistory === true;
137
- const historyEntries = lazyOlderHistory ? undefined : [];
137
+ const historyEntries = [];
138
+ let historyAttached = false;
138
139
  const messages = lazyOlderHistory
139
140
  ? sessionHistoryDisplayMessages(runtime.session)
140
141
  : sessionHistoryDisplayMessagesFromEntries(await sessionHistoryFullBranchEntries(runtime.session));
@@ -142,24 +143,22 @@ export class AppSessionEventController {
142
143
  return false;
143
144
  const loaded = await loadSessionHistoryEntriesAsync({
144
145
  messages,
145
- addEntry: (entry) => {
146
- if (historyEntries)
147
- historyEntries.push(entry);
148
- else
149
- this.addEntry(entry);
150
- },
146
+ addEntry: (entry) => historyEntries.push(entry),
151
147
  prependEntries: (entries) => {
152
- if (historyEntries)
153
- historyEntries.unshift(...entries);
148
+ if (historyAttached)
149
+ this.prependLoadedHistoryEntries(entries);
154
150
  else
155
- this.prependEntries(entries);
151
+ historyEntries.unshift(...entries);
156
152
  },
157
153
  setToolEntryId: (toolCallId, entryId) => this.toolEntryIdsByCallId.set(toolCallId, entryId),
158
154
  toolDefaultExpanded: (toolName) => this.host.toolDefaultExpanded(toolName),
159
155
  observeSubagentsToolResult: (toolName, details, options) => this.host.observeSubagentsToolResult(toolName, details, options),
160
156
  observeTodoToolResult: (toolName, details, isError) => this.host.observeTodoToolResult(toolName, details, isError),
161
157
  isCancelled: options.isCancelled,
162
- render: lazyOlderHistory ? options.render : () => { },
158
+ render: () => {
159
+ if (historyAttached)
160
+ options.render();
161
+ },
163
162
  lazyOlderHistory,
164
163
  olderMessagesReader: lazyOlderHistory ? sessionHistoryOlderMessagesReader(runtime.session) : undefined,
165
164
  onOlderLoaderReady: (loader) => {
@@ -169,24 +168,24 @@ export class AppSessionEventController {
169
168
  });
170
169
  if (!loaded)
171
170
  return false;
172
- if (!historyEntries)
173
- return !options.isCancelled();
174
171
  this.historyEntries = historyEntries;
172
+ historyAttached = true;
175
173
  this.setHistoryWindowStart(this.maxHistoryWindowStart());
176
174
  options.render();
177
175
  return !options.isCancelled();
178
176
  }
179
177
  hasOlderSessionHistory() {
180
178
  if (this.historyEntries.length > 0)
181
- return this.historyWindowStart > 0;
179
+ return this.historyWindowStart > 0 || this.olderHistoryLoader?.hasOlder() === true;
182
180
  return this.olderHistoryLoader?.hasOlder() === true;
183
181
  }
184
182
  isLoadingOlderSessionHistory() {
185
183
  return this.olderHistoryLoader?.isLoading() === true;
186
184
  }
187
185
  async loadOlderSessionHistory(options = {}) {
188
- if (this.historyEntries.length > 0)
186
+ if (this.historyEntries.length > 0 && this.historyWindowStart > 0) {
189
187
  return this.shiftHistoryWindow(-HISTORY_WINDOW_SHIFT_ENTRIES, options);
188
+ }
190
189
  return this.olderHistoryLoader?.loadOlder(options) ?? false;
191
190
  }
192
191
  /**
@@ -447,6 +446,14 @@ export class AppSessionEventController {
447
446
  this.registerEntry(entry);
448
447
  this.pruneHistoryWindow("bottom");
449
448
  }
449
+ prependLoadedHistoryEntries(entries) {
450
+ if (entries.length === 0)
451
+ return;
452
+ const previousStart = this.historyWindowStart;
453
+ this.historyEntries.unshift(...entries);
454
+ const nextStart = previousStart === 0 ? 0 : previousStart + entries.length;
455
+ this.setHistoryWindowStart(nextStart);
456
+ }
450
457
  shiftHistoryWindow(delta, options = {}) {
451
458
  if (this.historyEntries.length === 0)
452
459
  return false;
@@ -26,6 +26,7 @@ export type AppSessionLifecycleHost = {
26
26
  createExtensionCommandContextActions(runtime: AgentSessionRuntime): ExtensionCommandContextActions;
27
27
  handleExtensionError(error: ExtensionError): void;
28
28
  handleSessionEvent(event: AgentSessionEvent): void;
29
+ bindAgentPause(session: AgentSession): void;
29
30
  addEntry(entry: Entry): void;
30
31
  setStatus(status: string): void;
31
32
  showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
@@ -73,6 +73,7 @@ export class AppSessionLifecycleController {
73
73
  async bindCurrentSession(options = {}) {
74
74
  const runtime = this.requireRuntime();
75
75
  const session = runtime.session;
76
+ this.host.bindAgentPause(session);
76
77
  const ownershipGeneration = this.advanceOwnership(runtime, session);
77
78
  const subscriptionGeneration = ++this.subscriptionGeneration;
78
79
  this.replacementHistoryGeneration += 1;
@@ -284,6 +284,7 @@ export type StatusLineLayout = {
284
284
  compactToolsWidget?: StatusCompactToolsWidgetLayout;
285
285
  terminalBellSoundWidget?: StatusTerminalBellSoundWidgetLayout;
286
286
  promptEnhancerWidget?: StatusPromptEnhancerWidgetLayout;
287
+ agentPauseWidget?: StatusAgentPauseWidgetLayout;
287
288
  voiceWidget?: StatusVoiceWidgetLayout;
288
289
  };
289
290
  export type StatusQuickScrollWidgetLayout = {
@@ -318,6 +319,10 @@ export type StatusPromptEnhancerWidgetLayout = {
318
319
  startColumn: number;
319
320
  endColumn: number;
320
321
  };
322
+ export type StatusAgentPauseWidgetLayout = {
323
+ startColumn: number;
324
+ endColumn: number;
325
+ };
321
326
  export type StatusTerminalBellSoundTarget = {
322
327
  row: number;
323
328
  startColumn: number;
@@ -589,6 +594,11 @@ export type StatusPromptEnhancerTarget = {
589
594
  startColumn: number;
590
595
  endColumn: number;
591
596
  };
597
+ export type StatusAgentPauseTarget = {
598
+ row: number;
599
+ startColumn: number;
600
+ endColumn: number;
601
+ };
592
602
  export type StatusUserJumpTarget = {
593
603
  row: number;
594
604
  startColumn: number;
package/dist/config.js CHANGED
@@ -55,7 +55,7 @@ const DEFAULT_OUTPUT_FILTERS = {
55
55
  };
56
56
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
57
57
  const DEFAULT_PROMPT_ENHANCER = {
58
- modelRef: "zai/glm-5-turbo",
58
+ modelRef: "openai-codex/gpt-5.6-luna",
59
59
  };
60
60
  const DEFAULT_AUTOCOMPLETE = {
61
61
  modelRef: "zai/glm-5-turbo",
@@ -52,9 +52,9 @@ export const DEFAULT_PIX_CONFIG_JSONC = String.raw `{
52
52
  "antigravity/antigravity-claude-*": "error"
53
53
  },
54
54
 
55
- "promptEnhancer": { "modelRef": "zai/glm-5-turbo" },
55
+ "promptEnhancer": { "modelRef": "openai-codex/gpt-5.6-luna" },
56
56
  "autocomplete": { "modelRef": "zai/glm-5-turbo", "debounceMs": 350, "timeoutMs": 3000, "maxTokens": 48, "maxPromptTokens": 1200, "includeRecentMessages": 0 },
57
- "sessionTitle": { "modelRef": "zai/glm-5-turbo", "fallbackModels": ["openai-codex/gpt-5.3-codex-spark"] },
57
+ "sessionTitle": { "modelRef": "openai-codex/gpt-5.6-luna", "fallbackModels": ["zai/glm-5-turbo"] },
58
58
  "dictation": {
59
59
  "language": "en",
60
60
  "languages": {
@@ -247,7 +247,7 @@ For an oh-my-openagent-style workflow, run `/ultrawork` or `/ulw` to ask the par
247
247
 
248
248
  ### Private browser QA and project auth
249
249
 
250
- The built-in `browser-qa` role runs on `openai-codex/gpt-5.4-mini`, with
250
+ The built-in `browser-qa` role runs on `openai-codex/gpt-5.6-luna`, with
251
251
  `antigravity/gemini-3-flash-preview` and then `zai/glm-5.3` as fallbacks. Its browser
252
252
  workflow is an explicit private skill under `src/async-subagents/private-skills/`,
253
253
  outside normal Pi skill discovery. The role's first-class `isolatedSkills` setting launches the child with
@@ -4,7 +4,7 @@
4
4
 
5
5
  Provide a cheap, fast `browser-qa` async-subagent that reproduces browser bugs
6
6
  and proves fixes with deterministic assertions plus screenshot, video, and trace
7
- evidence. The role uses `openai-codex/gpt-5.4-mini`, falling back to
7
+ evidence. The role uses `openai-codex/gpt-5.6-luna`, falling back to
8
8
  `antigravity/gemini-3-flash-preview` and then `zai/glm-5.3`.
9
9
 
10
10
  ## Private skill isolation
@@ -62,6 +62,9 @@ evidence. The role uses `openai-codex/gpt-5.4-mini`, falling back to
62
62
  trusted runner implements a bounded set of navigation, interaction,
63
63
  assertion, screenshot, and auth-rejection actions and never gives the flow a
64
64
  Playwright context or credential values.
65
+ - Target discovery is a bounded preflight, not an open-ended research task. The
66
+ sub-agent invokes the runner within 45 seconds or returns `BLOCKED`; it does
67
+ not spend the full launcher budget reading source or probing prerequisites.
65
68
  - The launcher injects `PI_SUBAGENT_AGENT_DIR`, pre-creates a private
66
69
  `browser-qa/flows/` workspace, and clears stale browser QA files when an agent
67
70
  id is reused. The runner validates the directory's project/type metadata and
@@ -79,6 +82,32 @@ evidence. The role uses `openai-codex/gpt-5.4-mini`, falling back to
79
82
  - Auth rejection discovered by a QA flow is reported through the
80
83
  `authRejectedIf` action so the parent gets an update-required status.
81
84
 
85
+ ## Reliability and shutdown contract
86
+
87
+ - The built-in `browser-qa` profile has a 120-second wall-clock budget unless
88
+ the caller explicitly supplies a task or spawn timeout. This bounds model
89
+ stalls as well as browser work.
90
+ - The trusted runner has its own bounded lifecycle. Browser launch, context
91
+ setup, auth, flow execution, evidence finalization, and browser shutdown must
92
+ not wait forever; a timeout reports the last started stage without exposing
93
+ flow contents or credentials.
94
+ - Trace sanitization runs in a memory-limited worker that can be terminated at
95
+ the cleanup deadline; synchronous archive work cannot defeat the watchdog.
96
+ - The launcher always writes a small sanitized `progress.jsonl` journal in the
97
+ agent directory. It records lifecycle/RPC event types and tool names, but not
98
+ prompts, tool arguments, tool results, model text, or secrets. The browser
99
+ runner writes similarly sanitized stage entries under its private workspace.
100
+ - On POSIX, newly launched agents own a process group. Settled, timed-out, and
101
+ explicitly stopped agents signal that group rather than only the Pi process;
102
+ timeout/settled shutdown escalates to `SIGKILL` after its grace period. On
103
+ Windows the existing recursive `taskkill /T /F` behavior remains in force.
104
+ - Process-tree cleanup is scoped to a launcher-created process-group marker so
105
+ an old or externally-created PID is never treated as an owned process group.
106
+ User browser sessions outside that group must not be signalled.
107
+ - Playwright can launch Chromium in its own POSIX process group. On runner
108
+ failure the runner snapshots and kills only its own descendants before it
109
+ exits, covering that detached browser tree without touching a user's browser.
110
+
82
111
  ## Acceptance criteria
83
112
 
84
113
  1. `browser-qa` resolves to the intended model/fallback and its self-contained
@@ -96,7 +125,10 @@ evidence. The role uses `openai-codex/gpt-5.4-mini`, falling back to
96
125
  deleting the run removes them while persistent auth config/state remains.
97
126
  6. Completed test runs report clickable screenshot, video, and trace links
98
127
  whenever those artifacts exist.
99
- 7. Suite tests/typecheck, host checks, and suite sync pass.
128
+ 7. Timeout tests identify the last browser stage, launcher progress remains
129
+ available when full RPC logging is disabled, and process-tree tests prove a
130
+ descendant is terminated without signalling unrelated processes.
131
+ 8. Suite tests/typecheck, host checks, and suite sync pass.
100
132
 
101
133
  ## Real-browser regression test
102
134
 
@@ -44,9 +44,9 @@
44
44
  "vscode-languageserver-protocol": "^3.17.5"
45
45
  },
46
46
  "peerDependencies": {
47
- "@earendil-works/pi-ai": "0.84.2",
48
- "@earendil-works/pi-coding-agent": "0.84.2",
49
- "@earendil-works/pi-tui": "0.84.2",
47
+ "@earendil-works/pi-ai": "0.84.3",
48
+ "@earendil-works/pi-coding-agent": "0.84.3",
49
+ "@earendil-works/pi-tui": "0.84.3",
50
50
  "typebox": "*"
51
51
  },
52
52
  "devDependencies": {
@@ -71,7 +71,7 @@
71
71
  "model": "zai/glm-4.5-air",
72
72
  // Ordered router model fallbacks tried when the primary routing model is
73
73
  // unavailable or fails. The current parent model is always tried last.
74
- "fallbackModels": ["openai-codex/gpt-5.3-codex-spark"],
74
+ "fallbackModels": ["openai-codex/gpt-5.6-luna"],
75
75
  "maxTaskChars": 1200,
76
76
  "maxTokens": 512,
77
77
  "maxRetries": 3,
@@ -116,7 +116,7 @@
116
116
  "research": { "model": "zai/glm-5-turbo", "thinking": "low" },
117
117
  "docs": { "model": "zai/glm-4.5-air", "thinking": "low" },
118
118
  "frontend": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["zai/glm-5.3"], "thinking": "medium" },
119
- "browser-qa": { "model": "openai-codex/gpt-5.4-mini", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "medium" },
119
+ "browser-qa": { "model": "openai-codex/gpt-5.6-luna", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "low" },
120
120
  "tests": { "model": "zai/glm-5-turbo", "thinking": "medium" },
121
121
  "review": { "model": "zai/glm-5.3", "thinking": "high" },
122
122
  "implement": { "model": "zai/glm-5.3", "thinking": "high" },
@@ -132,7 +132,7 @@
132
132
  "research": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5-turbo"], "thinking": "low" },
133
133
  "docs": { "model": "openai-codex/gpt-5.6-luna", "fallbackModels": ["zai/glm-4.5-air"], "thinking": "low" },
134
134
  "frontend": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "medium" },
135
- "browser-qa": { "model": "openai-codex/gpt-5.4-mini", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "medium" },
135
+ "browser-qa": { "model": "openai-codex/gpt-5.6-luna", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "low" },
136
136
  "tests": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5-turbo"], "thinking": "medium" },
137
137
  "review": { "model": "openai-codex/gpt-5.6-sol", "fallbackModels": ["zai/glm-5.3"], "thinking": "high" },
138
138
  "implement": { "model": "openai-codex/gpt-5.6-sol", "fallbackModels": ["zai/glm-5.3"], "thinking": "high" },
@@ -143,13 +143,13 @@
143
143
  "deep": {
144
144
  "description": "Use only enabled strong models, mixing GPT, Claude, and Gemini by role.",
145
145
  "types": {
146
- "quick": { "model": "openai-codex/gpt-5.4-mini", "fallbackModels": ["zai/glm-4.5-air"], "thinking": "low" },
147
- "scan": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["openai-codex/gpt-5.3-codex-spark", "zai/glm-4.5-air"], "thinking": "off" },
148
- "research": { "model": "antigravity/gemini-3.1-pro-preview", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5-turbo"], "thinking": "medium" },
149
- "docs": { "model": "antigravity/gemini-2.5-flash", "fallbackModels": ["openai-codex/gpt-5.3-codex-spark", "zai/glm-4.5-air"], "thinking": "medium" },
150
- "frontend": { "model": "antigravity/gemini-3.1-pro-preview-customtools", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5.3"], "thinking": "low" },
151
- "browser-qa": { "model": "openai-codex/gpt-5.4-mini", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "medium" },
152
- "tests": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5-turbo"], "thinking": "high" },
146
+ "quick": { "model": "openai-codex/gpt-5.6-luna", "fallbackModels": ["zai/glm-4.5-air"], "thinking": "low" },
147
+ "scan": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["openai-codex/gpt-5.6-luna", "zai/glm-4.5-air"], "thinking": "off" },
148
+ "research": { "model": "antigravity/gemini-3.1-pro-preview", "fallbackModels": ["openai-codex/gpt-5.6-luna", "zai/glm-5-turbo"], "thinking": "medium" },
149
+ "docs": { "model": "antigravity/gemini-2.5-flash", "fallbackModels": ["openai-codex/gpt-5.6-luna", "zai/glm-4.5-air"], "thinking": "medium" },
150
+ "frontend": { "model": "antigravity/gemini-3.1-pro-preview-customtools", "fallbackModels": ["openai-codex/gpt-5.6-luna", "zai/glm-5.3"], "thinking": "low" },
151
+ "browser-qa": { "model": "openai-codex/gpt-5.6-luna", "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"], "thinking": "low" },
152
+ "tests": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.6-luna", "zai/glm-5-turbo"], "thinking": "high" },
153
153
  "review": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.6-sol", "zai/glm-5.3"], "thinking": "high" },
154
154
  "implement": { "model": "openai-codex/gpt-5.6-sol", "fallbackModels": ["zai/glm-5.3"], "thinking": "high" },
155
155
  "deep": { "model": "antigravity/antigravity-claude-opus-4-6-thinking", "fallbackModels": ["openai-codex/gpt-5.6-sol", "zai/glm-5.3"], "thinking": "high" }
@@ -160,34 +160,38 @@
160
160
  "types": {
161
161
  "quick": {
162
162
  "description": "Use for tiny cheap tasks: answer a simple question, inspect one known file, or verify one fact. Not for broad repo search.",
163
- "model": "zai/glm-5-turbo",
163
+ "model": "openai-codex/gpt-5.6-luna",
164
+ "fallbackModels": ["zai/glm-4.5-air"],
164
165
  "thinking": "off"
165
166
  },
166
167
 
167
168
  "scan": {
168
169
  "description": "Use for finding files, symbols, text, or inventory across a repo. Return paths/facts; do not judge code quality.",
169
- "model": "zai/glm-5-turbo",
170
+ "model": "openai-codex/gpt-5.6-luna",
171
+ "fallbackModels": ["zai/glm-4.5-air"],
170
172
  "thinking": "off",
171
173
  "tools": ["read", "grep"]
172
174
  },
173
175
 
174
176
  "research": {
175
177
  "description": "Use for multi-file codebase research: read several files and explain how something works. No edits.",
176
- "model": "zai/glm-5-turbo",
178
+ "model": "openai-codex/gpt-5.6-terra",
179
+ "fallbackModels": ["zai/glm-5-turbo"],
177
180
  "thinking": "low",
178
181
  "tools": ["read", "grep"]
179
182
  },
180
183
 
181
184
  "docs": {
182
185
  "description": "Use for documentation work: README/API docs review, docs gaps, changelog, migration notes, examples.",
183
- "model": "zai/glm-5-turbo",
186
+ "model": "openai-codex/gpt-5.6-luna",
187
+ "fallbackModels": ["zai/glm-4.5-air"],
184
188
  "thinking": "low"
185
189
  },
186
190
 
187
191
  "frontend": {
188
192
  "description": "Use for frontend UI/UX visual work: styling, layout, typography, animation, responsive states, component polish, accessibility. Avoid backend/business logic unless needed for UI behavior.",
189
193
  "model": "antigravity/gemini-3-flash-preview",
190
- "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5.3"],
194
+ "fallbackModels": ["openai-codex/gpt-5.6-luna", "zai/glm-5.3"],
191
195
  "thinking": "medium",
192
196
  "promptAppend": [
193
197
  "Act as a frontend UI/UX engineer for visual and product-facing work.",
@@ -199,15 +203,17 @@
199
203
 
200
204
  "browser-qa": {
201
205
  "description": "Use for browser-based visual QA: reproduce UI bugs and verify fixes with deterministic assertions, screenshots, video, and traces.",
202
- "model": "openai-codex/gpt-5.4-mini",
206
+ "model": "openai-codex/gpt-5.6-luna",
203
207
  "fallbackModels": ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"],
204
- "thinking": "medium",
208
+ "thinking": "low",
209
+ "timeoutMs": 120000,
205
210
  "tools": ["read", "grep", "bash"]
206
211
  },
207
212
 
208
213
  "tests": {
209
214
  "description": "Use for tests: locate coverage, find gaps, run/check targeted test commands, diagnose failing tests.",
210
- "model": "zai/glm-5-turbo",
215
+ "model": "openai-codex/gpt-5.6-terra",
216
+ "fallbackModels": ["zai/glm-5-turbo"],
211
217
  "thinking": "medium",
212
218
  "tools": ["read", "grep", "bash"]
213
219
  },
@@ -167,7 +167,7 @@ export const DEFAULT_MAX_CONCURRENT = 5;
167
167
  export const DEFAULT_ROUTING_CONFIG: ResolvedSubagentRoutingConfig = {
168
168
  enabled: true,
169
169
  model: "zai/glm-4.5-air",
170
- fallbackModels: ["openai-codex/gpt-5.3-codex-spark"],
170
+ fallbackModels: ["openai-codex/gpt-5.6-luna"],
171
171
  maxTaskChars: 1200,
172
172
  maxTokens: 512,
173
173
  maxRetries: 3,
@@ -181,18 +181,26 @@ const BUILTIN_CONFIG: SubagentConfig = {
181
181
  types: {
182
182
  quick: {
183
183
  description: "Use for tiny cheap tasks: answer a simple question, inspect one known file, or verify one fact. Not for broad repo search.",
184
+ model: "openai-codex/gpt-5.6-luna",
185
+ fallbackModels: ["zai/glm-4.5-air"],
184
186
  thinking: "off",
185
187
  },
186
188
  scan: {
187
189
  description: "Use for finding files, symbols, text, or inventory across a repo. Return paths/facts; do not judge code quality.",
190
+ model: "openai-codex/gpt-5.6-luna",
191
+ fallbackModels: ["zai/glm-4.5-air"],
188
192
  thinking: "off",
189
193
  },
190
194
  research: {
191
195
  description: "Use for multi-file codebase research: read several files and explain how something works. No edits.",
196
+ model: "openai-codex/gpt-5.6-terra",
197
+ fallbackModels: ["zai/glm-5-turbo"],
192
198
  thinking: "low",
193
199
  },
194
200
  docs: {
195
201
  description: "Use for documentation work: README/API docs review, docs gaps, changelog, migration notes, examples.",
202
+ model: "openai-codex/gpt-5.6-luna",
203
+ fallbackModels: ["zai/glm-4.5-air"],
196
204
  thinking: "low",
197
205
  },
198
206
  frontend: {
@@ -207,9 +215,10 @@ const BUILTIN_CONFIG: SubagentConfig = {
207
215
  },
208
216
  "browser-qa": {
209
217
  description: "Use for browser-based visual QA: reproduce UI bugs and verify fixes with deterministic assertions, screenshots, video, and traces.",
210
- model: "openai-codex/gpt-5.4-mini",
218
+ model: "openai-codex/gpt-5.6-luna",
211
219
  fallbackModels: ["antigravity/gemini-3-flash-preview", "zai/glm-5.3"],
212
- thinking: "medium",
220
+ thinking: "low",
221
+ timeoutMs: 120_000,
213
222
  tools: ["read", "grep", "bash"],
214
223
  isolatedSkills: [getBrowserQaSkillPath()],
215
224
  },
@@ -219,6 +228,8 @@ const BUILTIN_CONFIG: SubagentConfig = {
219
228
  },
220
229
  tests: {
221
230
  description: "Use for tests: locate coverage, find gaps, run/check targeted test commands, diagnose failing tests.",
231
+ model: "openai-codex/gpt-5.6-terra",
232
+ fallbackModels: ["zai/glm-5-turbo"],
222
233
  thinking: "medium",
223
234
  },
224
235
  review: {
@@ -13,3 +13,22 @@ export function terminateProcess(pid: number, signal: ProcessSignal): void {
13
13
  }
14
14
  process.kill(pid, signal);
15
15
  }
16
+
17
+ /** Signal a launcher-owned process tree. POSIX callers must create the child as
18
+ * a process-group leader before using this helper. */
19
+ export function terminateProcessTree(pid: number, signal: ProcessSignal): void {
20
+ if (process.platform === "win32") {
21
+ terminateProcess(pid, signal);
22
+ return;
23
+ }
24
+
25
+ try {
26
+ process.kill(-pid, signal);
27
+ } catch (error) {
28
+ const code = typeof error === "object" && error && "code" in error
29
+ ? String((error as { code?: unknown }).code)
30
+ : undefined;
31
+ if (code !== "ESRCH") throw error;
32
+ // The owned group is already gone. Never fall back to a possibly reused PID.
33
+ }
34
+ }