@gajae-code/coding-agent 0.16.0 → 0.16.1
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.
- package/CHANGELOG.md +41 -1
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/model-registry.d.ts +16 -0
- package/dist/types/config/settings-schema.d.ts +17 -7
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -1
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +17 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +3 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/rows.d.ts +8 -2
- package/dist/types/sdk/cli/session-cli.d.ts +2 -1
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +18 -17
- package/dist/types/sdk/lifecycle-session.d.ts +1 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +4 -0
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +47 -7
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +14 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +12 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/index.d.ts +6 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +7 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/update-cli.ts +63 -12
- package/src/commands/sdk.ts +22 -7
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/model-preset-registry.ts +177 -18
- package/src/config/model-registry.ts +268 -42
- package/src/config/settings-schema.ts +19 -7
- package/src/config/settings.ts +46 -15
- package/src/coordinator-mcp/projection-scan.ts +6 -6
- package/src/coordinator-mcp/server.ts +21 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/session-state-lock.ts +154 -28
- package/src/gjc-runtime/session-state-sidecar.ts +10 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +4 -1
- package/src/modes/acp/acp-agent.ts +5 -5
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/controllers/selector-controller.ts +10 -3
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +10 -1
- package/src/sdk/broker/ensure.ts +76 -3
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +254 -34
- package/src/sdk/broker/spawn-substrate.ts +45 -2
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +17 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +33 -3
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +33 -21
- package/src/sdk/host/control/dispatch.ts +2 -2
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +2 -0
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +463 -8
- package/src/sdk/host/session-runtime.ts +383 -54
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle-session.ts +7 -2
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +54 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +152 -59
- package/src/sdk/session.ts +60 -41
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1033 -260
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/session-manager.ts +517 -77
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +156 -33
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/index.ts +6 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
CombinedAutocompleteProvider,
|
|
7
7
|
extractSlashCommandTokenPrefix,
|
|
8
8
|
getSlashCommandMatchRank,
|
|
9
|
+
hasInlineCodeDelimiter,
|
|
10
|
+
isInsideInlineCodeSpan,
|
|
9
11
|
normalizeFuzzyText,
|
|
10
12
|
type SlashCommand,
|
|
11
13
|
} from "@gajae-code/tui";
|
|
@@ -140,7 +142,7 @@ function withoutSkillCommandSuggestions(
|
|
|
140
142
|
suggestions: { items: AutocompleteItem[]; prefix: string } | null,
|
|
141
143
|
): { items: AutocompleteItem[]; prefix: string } | null {
|
|
142
144
|
if (!suggestions) return null;
|
|
143
|
-
const items = suggestions.items.filter(item => !item.value.startsWith("skill:"));
|
|
145
|
+
const items = suggestions.items.filter(item => !item.value.toLowerCase().startsWith("skill:"));
|
|
144
146
|
return items.length > 0 ? { ...suggestions, items } : null;
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -160,6 +162,21 @@ function getSlashTokenPrefix(textBeforeCursor: string): string | null {
|
|
|
160
162
|
return extractSlashCommandTokenPrefix(textBeforeCursor);
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
function getRawSlashTokenPrefix(textBeforeCursor: string): string | null {
|
|
166
|
+
return textBeforeCursor.match(/(?:^|\s)(\/[^\s]*)$/)?.[1] ?? null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isSlashTokenInsideInlineCodeSpan(
|
|
170
|
+
lines: string[],
|
|
171
|
+
cursorLine: number,
|
|
172
|
+
textBeforeCursor: string,
|
|
173
|
+
prefix: string,
|
|
174
|
+
): boolean {
|
|
175
|
+
const tokenStart = textBeforeCursor.length - prefix.length;
|
|
176
|
+
const textBeforeToken = [...lines.slice(0, cursorLine), textBeforeCursor.slice(0, tokenStart)].join("\n");
|
|
177
|
+
return isInsideInlineCodeSpan(textBeforeToken);
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
164
181
|
#baseProvider: CombinedAutocompleteProvider;
|
|
165
182
|
#actions: PromptActionDefinition[];
|
|
@@ -211,8 +228,13 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
211
228
|
}
|
|
212
229
|
}
|
|
213
230
|
|
|
231
|
+
const rawSlashPrefix = getRawSlashTokenPrefix(textBeforeCursor);
|
|
214
232
|
const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
|
|
215
|
-
|
|
233
|
+
const slashTokenInsideCode =
|
|
234
|
+
rawSlashPrefix !== null &&
|
|
235
|
+
(hasInlineCodeDelimiter(rawSlashPrefix) ||
|
|
236
|
+
isSlashTokenInsideInlineCodeSpan(lines, cursorLine, textBeforeCursor, rawSlashPrefix));
|
|
237
|
+
if (slashPrefix && !slashTokenInsideCode) {
|
|
216
238
|
const baseSuggestions = withoutSkillCommandSuggestions(
|
|
217
239
|
await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol),
|
|
218
240
|
);
|
|
@@ -225,6 +247,9 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
225
247
|
this.#commands,
|
|
226
248
|
);
|
|
227
249
|
}
|
|
250
|
+
if (rawSlashPrefix && slashTokenInsideCode) {
|
|
251
|
+
return withoutSkillCommandSuggestions(await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol));
|
|
252
|
+
}
|
|
228
253
|
|
|
229
254
|
if (!isSettingsInitialized() || settings.get("emojiAutocomplete")) {
|
|
230
255
|
const emojiSuggestions = getEmojiSuggestions(textBeforeCursor);
|
|
@@ -282,12 +307,13 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
282
307
|
const currentLine = lines[cursorLine] || "";
|
|
283
308
|
const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
|
|
284
309
|
const afterCursor = currentLine.slice(cursorCol);
|
|
310
|
+
const separator = afterCursor.length === 0 ? " " : "";
|
|
285
311
|
const newLines = [...lines];
|
|
286
|
-
newLines[cursorLine] = `${beforePrefix}/${item.value}
|
|
312
|
+
newLines[cursorLine] = `${beforePrefix}/${item.value}${separator}${afterCursor}`;
|
|
287
313
|
return {
|
|
288
314
|
lines: newLines,
|
|
289
315
|
cursorLine,
|
|
290
|
-
cursorCol: beforePrefix.length + item.value.length +
|
|
316
|
+
cursorCol: beforePrefix.length + item.value.length + 1 + separator.length,
|
|
291
317
|
};
|
|
292
318
|
}
|
|
293
319
|
return this.#baseProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
syntheticNamespaceCollision,
|
|
19
19
|
} from "../sdk/model-profile-model";
|
|
20
20
|
import type { AgentSession } from "../session/agent-session";
|
|
21
|
+
import { bashBackgroundControlError } from "../session/fold-coordinator";
|
|
21
22
|
import { parseThinkingLevel } from "../thinking";
|
|
22
23
|
import type { TodoPhase } from "../tools/todo-write";
|
|
23
24
|
|
|
@@ -68,8 +69,12 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
68
69
|
runner.initialize(
|
|
69
70
|
// ExtensionActions
|
|
70
71
|
{
|
|
72
|
+
// Extension sends are INDEPENDENT producers, never continuations of the
|
|
73
|
+
// current turn: the trusted adapter classifies them so a terminal abort
|
|
74
|
+
// preserves them instead of dropping them as turn-owned work. Extensions
|
|
75
|
+
// never supply this bit themselves.
|
|
71
76
|
sendMessage: (message, sendOptions) => {
|
|
72
|
-
session.sendCustomMessage(message, sendOptions).catch(e => {
|
|
77
|
+
session.sendCustomMessage(message, { ...sendOptions, origin: "external" }).catch(e => {
|
|
73
78
|
reportSendError("extension_send", e instanceof Error ? e : new Error(String(e)));
|
|
74
79
|
});
|
|
75
80
|
},
|
|
@@ -154,8 +159,8 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
154
159
|
session.setFollowUpMode(mode);
|
|
155
160
|
return true;
|
|
156
161
|
}
|
|
157
|
-
if (kind === "
|
|
158
|
-
session.
|
|
162
|
+
if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
|
|
163
|
+
session.setToolInterruptPolicy(mode);
|
|
159
164
|
return true;
|
|
160
165
|
}
|
|
161
166
|
return false;
|
|
@@ -195,6 +200,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
195
200
|
return { bytes: new Uint8Array(await file.slice(start, end).arrayBuffer()), totalBytes: file.size };
|
|
196
201
|
},
|
|
197
202
|
getJobs: () => session.getAsyncJobSnapshot(),
|
|
203
|
+
onJobFold: listener => session.onJobFold(listener),
|
|
198
204
|
setSdkPermissionProvider: provider => session.setSdkPermissionProvider(provider),
|
|
199
205
|
setSdkClientBridge: bridge => session.setClientBridge(bridge),
|
|
200
206
|
sdkControl: async (operation, input) => {
|
|
@@ -343,12 +349,9 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
343
349
|
return { retried: true, immediate: true };
|
|
344
350
|
}
|
|
345
351
|
case "bash.background": {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
{ code: "not_foldable" },
|
|
350
|
-
);
|
|
351
|
-
return { backgrounded: true };
|
|
352
|
+
const outcome = await session.requestForegroundBashBackgroundOutcome("sdk_control");
|
|
353
|
+
if (outcome.status !== "folded") throw bashBackgroundControlError(outcome);
|
|
354
|
+
return { backgrounded: true, jobId: outcome.jobId };
|
|
352
355
|
}
|
|
353
356
|
case "compaction.auto.set":
|
|
354
357
|
session.setAutoCompactionEnabled(input.on === true);
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -136,6 +136,7 @@ export type SymbolKey =
|
|
|
136
136
|
| "icon.output"
|
|
137
137
|
| "icon.host"
|
|
138
138
|
| "icon.session"
|
|
139
|
+
| "icon.star"
|
|
139
140
|
| "icon.package"
|
|
140
141
|
| "icon.warning"
|
|
141
142
|
| "icon.rewind"
|
|
@@ -300,6 +301,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
300
301
|
"icon.output": "⤴",
|
|
301
302
|
"icon.host": "🖥",
|
|
302
303
|
"icon.session": "🆔",
|
|
304
|
+
"icon.star": "★",
|
|
303
305
|
"icon.package": "📦",
|
|
304
306
|
"icon.warning": "⚠",
|
|
305
307
|
"icon.rewind": "↶",
|
|
@@ -534,6 +536,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
534
536
|
"icon.host": "\uf109",
|
|
535
537
|
// pick: | alt:
|
|
536
538
|
"icon.session": "\uf550",
|
|
539
|
+
"icon.star": "\uf005",
|
|
537
540
|
// pick: | alt:
|
|
538
541
|
"icon.package": "\uf487",
|
|
539
542
|
// pick: | alt:
|
|
@@ -722,6 +725,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
722
725
|
"icon.output": "out:",
|
|
723
726
|
"icon.host": "host",
|
|
724
727
|
"icon.session": "id",
|
|
728
|
+
"icon.star": "*",
|
|
725
729
|
"icon.package": "[P]",
|
|
726
730
|
"icon.warning": "[!]",
|
|
727
731
|
"icon.rewind": "<-",
|
|
@@ -1496,6 +1500,7 @@ export class Theme {
|
|
|
1496
1500
|
output: this.#symbols["icon.output"],
|
|
1497
1501
|
host: this.#symbols["icon.host"],
|
|
1498
1502
|
session: this.#symbols["icon.session"],
|
|
1503
|
+
star: this.#symbols["icon.star"],
|
|
1499
1504
|
package: this.#symbols["icon.package"],
|
|
1500
1505
|
warning: this.#symbols["icon.warning"],
|
|
1501
1506
|
rewind: this.#symbols["icon.rewind"],
|
package/src/modes/types.ts
CHANGED
|
@@ -41,6 +41,8 @@ export type CompactionQueuedMessage = {
|
|
|
41
41
|
text: string;
|
|
42
42
|
mode: "steer" | "followUp";
|
|
43
43
|
followUpQueuePolicy?: "sequential";
|
|
44
|
+
/** Composer steers stay one per poll even under `steeringMode: "all"`. */
|
|
45
|
+
steerQueuePolicy?: "sequential";
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
export type SubmittedUserInput = {
|
|
@@ -1091,6 +1091,11 @@ export class UiHelpers {
|
|
|
1091
1091
|
|
|
1092
1092
|
queueCompactionMessage(text: string, mode: "steer" | "followUp", options?: ComposerSubmissionOptions): void {
|
|
1093
1093
|
const entry: CompactionQueuedMessage = { text, mode };
|
|
1094
|
+
// A composer steer keeps the same one-per-poll delivery it would have had
|
|
1095
|
+
// without compaction: the mark has to survive the park/replay round trip.
|
|
1096
|
+
if (mode === "steer") {
|
|
1097
|
+
entry.steerQueuePolicy = "sequential";
|
|
1098
|
+
}
|
|
1094
1099
|
if (mode === "followUp") {
|
|
1095
1100
|
entry.followUpQueuePolicy = "sequential";
|
|
1096
1101
|
}
|
|
@@ -1115,6 +1120,11 @@ export class UiHelpers {
|
|
|
1115
1120
|
return message.followUpQueuePolicy ?? "sequential";
|
|
1116
1121
|
}
|
|
1117
1122
|
|
|
1123
|
+
#compactionSteerQueuePolicy(message: CompactionQueuedMessage): "sequential" | undefined {
|
|
1124
|
+
if (message.mode !== "steer") return undefined;
|
|
1125
|
+
return message.steerQueuePolicy ?? "sequential";
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1118
1128
|
async #deliverQueuedSkillMessage(message: CompactionQueuedMessage): Promise<boolean> {
|
|
1119
1129
|
const invocations = parseSkillInvocations(message.text, this.ctx.skillCommands ?? new Map());
|
|
1120
1130
|
if (invocations.length === 0) {
|
|
@@ -1163,7 +1173,10 @@ export class UiHelpers {
|
|
|
1163
1173
|
streamingBehavior: message.mode,
|
|
1164
1174
|
followUpQueuePolicy: this.#compactionFollowUpQueuePolicy(message),
|
|
1165
1175
|
}
|
|
1166
|
-
: {
|
|
1176
|
+
: {
|
|
1177
|
+
streamingBehavior: message.mode,
|
|
1178
|
+
steerQueuePolicy: this.#compactionSteerQueuePolicy(message),
|
|
1179
|
+
};
|
|
1167
1180
|
await this.ctx.session.promptCustomMessage(
|
|
1168
1181
|
{
|
|
1169
1182
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
@@ -1196,7 +1209,10 @@ export class UiHelpers {
|
|
|
1196
1209
|
? this.ctx.session.followUp(message.text, undefined, {
|
|
1197
1210
|
followUpQueuePolicy: this.#compactionFollowUpQueuePolicy(message),
|
|
1198
1211
|
})
|
|
1199
|
-
: this.ctx.session.
|
|
1212
|
+
: this.ctx.session.prompt(message.text, {
|
|
1213
|
+
streamingBehavior: "steer",
|
|
1214
|
+
steerQueuePolicy: this.#compactionSteerQueuePolicy(message),
|
|
1215
|
+
}),
|
|
1200
1216
|
);
|
|
1201
1217
|
}
|
|
1202
1218
|
|
|
@@ -1310,7 +1326,10 @@ export class UiHelpers {
|
|
|
1310
1326
|
streamingBehavior: "followUp" as const,
|
|
1311
1327
|
followUpQueuePolicy: this.#compactionFollowUpQueuePolicy(firstPrompt),
|
|
1312
1328
|
}
|
|
1313
|
-
: {
|
|
1329
|
+
: {
|
|
1330
|
+
streamingBehavior: "steer" as const,
|
|
1331
|
+
steerQueuePolicy: this.#compactionSteerQueuePolicy(firstPrompt),
|
|
1332
|
+
};
|
|
1314
1333
|
const promptPromise = this.ctx.session.prompt(firstPrompt.text, firstPromptOptions).catch((error: unknown) => {
|
|
1315
1334
|
disposeFirstPrompt();
|
|
1316
1335
|
restoreQueue(error);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<browser-backend>
|
|
2
|
+
Browser backend: Aside direct CLI. The built-in Puppeteer browser tool is disabled by configuration (`browser.backend: aside`). NEVER use or register an MCP browser server.
|
|
3
|
+
|
|
4
|
+
Routing:
|
|
5
|
+
- Every task that requires a rendered page, authenticated/private browser state, live tabs, browser UI, screenshots, downloads behind cookies, history, saved credentials, or profile data MUST use Bash to invoke the installed Aside CLI.
|
|
6
|
+
- Default to `aside repl '<JavaScript>'` for deterministic Playwright inspection and actions, even when the answer is unknown but the procedure is discoverable from `snapshot(page, { interactive: true })`.
|
|
7
|
+
- Use `aside exec '<prompt>'` only for runtime judgment, source-heavy research, browser-history search, MFA or opaque password autofill, approval/notification waits, long monitoring, multi-site synthesis, routines, or work worth a second model. Continue with `aside exec --session <id> '<follow-up>'`. Use `--account`, `--model`, `--provider`, `--speed`, or `--effort` only when the task requires overriding user defaults.
|
|
8
|
+
- Cookie-free public HTTP content that does not require rendered or authenticated browser state may use GJC `read` directly.
|
|
9
|
+
|
|
10
|
+
Deterministic procedure:
|
|
11
|
+
- Each non-interactive `aside repl '<code>'` shell invocation starts from neutral local JavaScript scope: make it self-contained and do not depend on variables declared in an earlier invocation.
|
|
12
|
+
- Call `listBrowserTabs()`, select by URL/title, `attachBrowserTab(targetId)`, then verify `page.url()` and `await page.title()` before reading or acting. Use `attachActiveBrowserTab()` only when active-tab authority is intentional; use `openTab()` only when no relevant tab exists.
|
|
13
|
+
- Print results with `console.log`; returned and final-expression values are not emitted. Use `snapshot(page, { interactive: true })` before acting, prefer locators/refs, and verify every meaningful action with a fresh URL/title, snapshot diff, screenshot, or file path.
|
|
14
|
+
- Use authenticated `fetch` for cookie-bearing downloads. Prefer dedicated integrations for Gmail, Google Docs/Sheets, YouTube, search, social, PDF, and Office files over manual UI automation.
|
|
15
|
+
- Use `chrome.*` only for task-scoped tabs/windows/bookmarks/tab-groups/history/downloads/top-sites profile operations. Use `cua.*` only when DOM semantics cannot control canvas or visual UI; screenshot before and verify after. Use `captcha.*` only in an authorized user-owned session where automation is permitted.
|
|
16
|
+
|
|
17
|
+
Safety:
|
|
18
|
+
- Aside controls a live authenticated profile. Never print cookies, authorization headers, passwords, OTPs, card values, recovery material, signed URLs, raw task transcripts, private profile paths, or broad inbox/contact/history/session collections.
|
|
19
|
+
- Read, inspect, draft, and preview by default. Sending/posting, accepting invitations, payments/purchases/subscriptions, credential saves, document edits, downloads to an external destination, settings/session/routine/channel changes, and destructive profile operations require explicit user intent for the exact target. Stop before the final side effect when authorization is ambiguous.
|
|
20
|
+
- Permission rules (`allow`, `ask`, `deny`), password access policy, target origin, and file/network/download boundaries remain authoritative and must never be bypassed.
|
|
21
|
+
- Load the installed GJC `aside` skill before a browser job when available; it contains the complete official API inventory and scenario matrix. Never claim browser work ran without the Aside CLI result and verified state.
|
|
22
|
+
</browser-backend>
|
package/src/prompts/tools/ask.md
CHANGED
|
@@ -17,7 +17,7 @@ Asks user when you need clarification or input during task execution.
|
|
|
17
17
|
<critical>
|
|
18
18
|
- **Default to action.** Resolve ambiguity yourself using repo conventions, existing patterns, and reasonable defaults. Exhaust existing sources (code, configs, docs, history) before asking. Only ask when options have materially different tradeoffs the user must decide.
|
|
19
19
|
- **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.
|
|
20
|
-
- **Do NOT include "Other"
|
|
20
|
+
- **Do NOT include the automatic custom-input control or labels that imitate it** such as "Other (type your own)", "Other (specify)", "type your own", "custom input", or "직접 입력" — UI automatically adds "Other (type your own)" to every question. A plain domain value such as "Other" is allowed when it is a genuine answer, not a request for free text. To collect free text, ask for it in the question body and rely on the automatic Other/custom-input control instead of adding a fake option.
|
|
21
21
|
</critical>
|
|
22
22
|
|
|
23
23
|
<examples>
|
|
@@ -18,6 +18,7 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
18
18
|
{{/if}}
|
|
19
19
|
{{#if foregroundFoldEnabled}}
|
|
20
20
|
- In the interactive TUI, the user can press the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via the `app.tool.backgroundFold` keybinding) while a foreground command is still running to fold it into a background job. All three command surfaces fold: managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded command keeps its original timeout and is never killed or restarted by folding; a folded PTY continues output-only, so it stops accepting input. The turn then ends cleanly and a later turn resumes automatically with the command's result, so the interrupted task is finished rather than abandoned. `task`/`subagent` waits are deliberately NOT foldable. Do not instruct users to use raw shell `Ctrl+Z`/`bg` inside the GJC TUI; ownership and output routing are not safe there.
|
|
21
|
+
- A user steer folds the same way: when the user sends a message while a foreground command has already run for at least 2 seconds, the command is folded into a background job and this call returns the background-start result plus the line `Folded into background job <id> because a user steer arrived; the command keeps running with its original timeout and its result will wake a later turn.` The steer is then delivered at this tool boundary, so handle it next; do not re-run the folded command. Steers that arrive within the first 2 seconds let the command finish normally. This applies only while `busyPromptMode` is `steer` and `toolInterruptPolicy` is `abort_tools`.
|
|
21
22
|
{{/if}}
|
|
22
23
|
</instruction>
|
|
23
24
|
{{#if restrictedAllowedPrefixes}}
|
package/src/prompts/tools/job.md
CHANGED
|
@@ -2,7 +2,7 @@ Inspects, waits, or cancels async jobs.
|
|
|
2
2
|
|
|
3
3
|
Background job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.
|
|
4
4
|
|
|
5
|
-
In the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.
|
|
5
|
+
In the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`), or automatically when the user sends a steer after the command has run for at least 2 seconds (with `busyPromptMode=steer` and `toolInterruptPolicy=abort_tools`). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. Job listings expose `foldReason` (`steer`, `chord`, `timer`, or `sdk_control`) beside `backgrounded` so you can tell why a job left the foreground. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.
|
|
6
6
|
|
|
7
7
|
# Operations
|
|
8
8
|
|
package/src/sdk/acp/adapter.ts
CHANGED
|
@@ -91,6 +91,11 @@ const ACP_MCP_PRESERVED_LAUNCH_CODES = new Set([
|
|
|
91
91
|
"startup_admission_timeout",
|
|
92
92
|
"ready_then_exited",
|
|
93
93
|
"endpoint_unreadable",
|
|
94
|
+
"worktree_preparation_timeout",
|
|
95
|
+
"dependency_preparation_timeout",
|
|
96
|
+
"readiness_timeout",
|
|
97
|
+
"spawn_failed",
|
|
98
|
+
"worktree_in_use",
|
|
94
99
|
]);
|
|
95
100
|
|
|
96
101
|
/**
|
package/src/sdk/broker/broker.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import type { NativeDirectoryTreeSnapshot } from "@gajae-code/natives";
|
|
7
7
|
import { logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
8
|
+
import packageJson from "../../../package.json" with { type: "json" };
|
|
8
9
|
import type { ModelProfileErrorDetails } from "../../config/model-profile-contract";
|
|
9
10
|
import { planLaunchWorktree } from "../../gjc-runtime/launch-worktree";
|
|
10
11
|
import { SdkClient, SdkClientError } from "../client";
|
|
@@ -101,6 +102,10 @@ type ResolvedBrokerSettings = {
|
|
|
101
102
|
spawnPromptLayer?: SpawnPromptLayer;
|
|
102
103
|
masterOrphanGraceMs: number;
|
|
103
104
|
};
|
|
105
|
+
export function resolveBrokerPackageGeneration(): string {
|
|
106
|
+
const v = (packageJson as { version?: unknown }).version;
|
|
107
|
+
return typeof v === "string" && v.length > 0 ? v : "unknown";
|
|
108
|
+
}
|
|
104
109
|
|
|
105
110
|
function modelResolutionCwd(input: Record<string, unknown>): string | undefined {
|
|
106
111
|
const cwd = typeof input.cwd === "string" ? input.cwd : undefined;
|
|
@@ -132,6 +137,10 @@ export type BrokerErrorCode =
|
|
|
132
137
|
| "startup_admission_timeout"
|
|
133
138
|
| "startup_admission_refused"
|
|
134
139
|
| "readiness_timeout"
|
|
140
|
+
| "worktree_preparation_timeout"
|
|
141
|
+
| "dependency_preparation_timeout"
|
|
142
|
+
| "worktree_in_use"
|
|
143
|
+
| "incarnation_unavailable"
|
|
135
144
|
| "close_refused"
|
|
136
145
|
| "not_found"
|
|
137
146
|
| "live_session"
|
|
@@ -1280,7 +1289,7 @@ export class Broker {
|
|
|
1280
1289
|
constructor(settings: BrokerSettings) {
|
|
1281
1290
|
this.settings = {
|
|
1282
1291
|
agentDir: settings.agentDir,
|
|
1283
|
-
packageGeneration: settings.packageGeneration ??
|
|
1292
|
+
packageGeneration: settings.packageGeneration ?? resolveBrokerPackageGeneration(),
|
|
1284
1293
|
port: settings.port ?? 0,
|
|
1285
1294
|
heartbeatTtlMs: settings.heartbeatTtlMs ?? BROKER_HEARTBEAT_TTL_MS,
|
|
1286
1295
|
resolveDirectoryMigration: settings.resolveDirectoryMigration ?? (async () => "copy-retain"),
|
package/src/sdk/broker/ensure.ts
CHANGED
|
@@ -3,9 +3,21 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import type { FileHandle } from "node:fs/promises";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import packageJson from "../../../package.json" with { type: "json" };
|
|
7
|
+
import { SdkClient } from "../client/client";
|
|
6
8
|
import { type BrokerDiscovery, brokerProcessIncarnation, readBrokerDiscovery } from "./discovery";
|
|
7
9
|
import { resolveSdkInternalSpawnCommand, type SdkInternalSpawnCommand } from "./runtime";
|
|
8
10
|
import { BrokerStartupError, clearBrokerStartupFailureMarker, readBrokerStartupFailureMarker } from "./startup-failure";
|
|
11
|
+
|
|
12
|
+
function resolveExpectedBrokerGeneration(): string {
|
|
13
|
+
const v = (packageJson as { version?: unknown }).version;
|
|
14
|
+
return typeof v === "string" && v.length > 0 ? v : "unknown";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isBrokerGenerationCompatible(discovery: BrokerDiscovery | null): boolean {
|
|
18
|
+
if (!discovery) return false;
|
|
19
|
+
return discovery.packageGeneration === resolveExpectedBrokerGeneration();
|
|
20
|
+
}
|
|
9
21
|
export interface EnsureBrokerSettings {
|
|
10
22
|
agentDir: string;
|
|
11
23
|
heartbeatTtlMs?: number;
|
|
@@ -248,6 +260,47 @@ function fixtureLeaseUnavailable(): Error {
|
|
|
248
260
|
return new Error("fixture_broker_lease_unavailable");
|
|
249
261
|
}
|
|
250
262
|
|
|
263
|
+
const STALE_BROKER_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
264
|
+
const STALE_BROKER_POLL_MS = 50;
|
|
265
|
+
|
|
266
|
+
type BrokerOwnerIdentity = Pick<BrokerDiscovery, "ownerId" | "pid" | "incarnation">;
|
|
267
|
+
|
|
268
|
+
function sameBrokerOwner(left: BrokerOwnerIdentity | null, right: BrokerOwnerIdentity): boolean {
|
|
269
|
+
return Boolean(
|
|
270
|
+
left && left.ownerId === right.ownerId && left.pid === right.pid && left.incarnation === right.incarnation,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function retireStaleBrokerGeneration(stale: BrokerDiscovery, settings: EnsureBrokerSettings): Promise<void> {
|
|
275
|
+
let shutdownSucceeded = false;
|
|
276
|
+
try {
|
|
277
|
+
const current = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
|
|
278
|
+
if (!current || !sameBrokerOwner(current, stale)) return;
|
|
279
|
+
const client = await SdkClient.connect(current.url, current.token, { timeoutMs: 2_000 });
|
|
280
|
+
try {
|
|
281
|
+
await client.global("broker.shutdown", {});
|
|
282
|
+
shutdownSucceeded = true;
|
|
283
|
+
} finally {
|
|
284
|
+
await client.close().catch(() => {});
|
|
285
|
+
}
|
|
286
|
+
} catch {}
|
|
287
|
+
if (!shutdownSucceeded) {
|
|
288
|
+
try {
|
|
289
|
+
if (brokerProcessIncarnation(stale.pid) === stale.incarnation) {
|
|
290
|
+
try {
|
|
291
|
+
process.kill(stale.pid, "SIGTERM");
|
|
292
|
+
} catch {}
|
|
293
|
+
}
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
// Wait for the stale identity to disappear (owner-fenced: pid+incarnation).
|
|
297
|
+
const deadline = Date.now() + STALE_BROKER_SHUTDOWN_TIMEOUT_MS;
|
|
298
|
+
while (Date.now() < deadline) {
|
|
299
|
+
const current = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
|
|
300
|
+
if (!sameBrokerOwner(current, stale)) break;
|
|
301
|
+
await sleep(STALE_BROKER_POLL_MS);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
251
304
|
function createFixtureLeaseFromChild(child: ChildProcess, terminate: () => Promise<void>): ExactFixtureBrokerLease {
|
|
252
305
|
let termination: Promise<void> | undefined;
|
|
253
306
|
const hasExited = (): boolean => child.exitCode !== null || child.signalCode !== null || child.pid === undefined;
|
|
@@ -300,11 +353,24 @@ async function ensureBrokerOnce(settings: EnsureBrokerSettings, initiator: Ensur
|
|
|
300
353
|
if (priorOwner.canReuse(existing)) return { kind: "prior-local-owner", discovery: existing!, owner: priorOwner };
|
|
301
354
|
await priorOwner.stop();
|
|
302
355
|
const discoveredAfterCleanup = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
|
|
303
|
-
if (discoveredAfterCleanup
|
|
356
|
+
if (discoveredAfterCleanup && isBrokerGenerationCompatible(discoveredAfterCleanup))
|
|
357
|
+
return { kind: "external-discovery", discovery: discoveredAfterCleanup };
|
|
358
|
+
// A stale-generation discovery after cleanup must not be reused — fall through to replacement.
|
|
359
|
+
if (!discoveredAfterCleanup) {
|
|
360
|
+
// no discovery left after cleanup — fall through to spawn
|
|
361
|
+
} else {
|
|
362
|
+
await retireStaleBrokerGeneration(discoveredAfterCleanup, settings);
|
|
363
|
+
const afterRetire = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
|
|
364
|
+
if (afterRetire && isBrokerGenerationCompatible(afterRetire))
|
|
365
|
+
return { kind: "external-discovery", discovery: afterRetire };
|
|
366
|
+
}
|
|
304
367
|
} else if (existing) {
|
|
305
|
-
return { kind: "external-discovery", discovery: existing };
|
|
368
|
+
if (isBrokerGenerationCompatible(existing)) return { kind: "external-discovery", discovery: existing };
|
|
369
|
+
await retireStaleBrokerGeneration(existing, settings);
|
|
370
|
+
const afterRetire = await readBrokerDiscovery(settings.agentDir, settings.heartbeatTtlMs);
|
|
371
|
+
if (afterRetire && isBrokerGenerationCompatible(afterRetire))
|
|
372
|
+
return { kind: "external-discovery", discovery: afterRetire };
|
|
306
373
|
}
|
|
307
|
-
|
|
308
374
|
const command = resolveSdkInternalSpawnCommand("broker-internal");
|
|
309
375
|
const spawnLog = await openBrokerSpawnLog(settings.agentDir);
|
|
310
376
|
// A stale marker must never be misattributed to this spawn; clear it first.
|
|
@@ -477,6 +543,13 @@ export function startFixtureBrokerCommandWithLeaseForTest(command: FixtureBroker
|
|
|
477
543
|
export function brokerOwnerForTest(agentDir: string): BrokerOwner | undefined {
|
|
478
544
|
return owners.get(agentDir);
|
|
479
545
|
}
|
|
546
|
+
/** Test hook: verifies the complete broker owner identity used during stale retirement. */
|
|
547
|
+
export function brokerOwnerIdentityMatchesForTest(
|
|
548
|
+
left: BrokerOwnerIdentity | null,
|
|
549
|
+
right: BrokerOwnerIdentity,
|
|
550
|
+
): boolean {
|
|
551
|
+
return sameBrokerOwner(left, right);
|
|
552
|
+
}
|
|
480
553
|
/** Test hook: drives the detached-broker reap on a controllable child surface. */
|
|
481
554
|
export function reapSpawnedBrokerForTest(child: ChildProcess, timing: ReapTiming = DEFAULT_REAP_TIMING): Promise<void> {
|
|
482
555
|
return reapSpawnedBroker(child, timing);
|
|
@@ -55,6 +55,7 @@ export interface LifecycleDurableEffectsReceipt {
|
|
|
55
55
|
cwdDigest: string;
|
|
56
56
|
created: boolean;
|
|
57
57
|
reused: boolean;
|
|
58
|
+
createdBranch: boolean;
|
|
58
59
|
branchDigest?: string;
|
|
59
60
|
};
|
|
60
61
|
transcript?: {
|
|
@@ -62,6 +63,11 @@ export interface LifecycleDurableEffectsReceipt {
|
|
|
62
63
|
contentDigest: string;
|
|
63
64
|
};
|
|
64
65
|
startup?: LifecycleStartupFailureReceipt;
|
|
66
|
+
timings?: {
|
|
67
|
+
worktreePreparationMs?: number;
|
|
68
|
+
dependencyPreparationMs?: number;
|
|
69
|
+
spawnAuthorizedAtOffsetMs?: number;
|
|
70
|
+
};
|
|
65
71
|
digest?: string;
|
|
66
72
|
}
|
|
67
73
|
|