@gajae-code/coding-agent 0.15.2 → 0.15.3
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 +37 -1
- package/dist/types/config/autorouting-tier-map.d.ts +0 -9
- package/dist/types/config/file-lock.d.ts +7 -1
- package/dist/types/config/keybindings.d.ts +13 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/model-resolver.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +13 -1
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +42 -2
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/action-registry.d.ts +1 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +16 -1
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +42 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -0
- package/dist/types/modes/shared/oauth-url-copy.d.ts +9 -0
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/ui-language.d.ts +2 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/broker/session-index.d.ts +18 -0
- package/dist/types/sdk/bus/index.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/cli/session-cli.d.ts +1 -1
- package/dist/types/sdk/lifecycle/service.d.ts +32 -5
- package/dist/types/sdk/session.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/fallback-chain-controller.d.ts +8 -0
- package/dist/types/slash-commands/helpers/aside.d.ts +54 -0
- package/dist/types/tools/ask.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +9 -1
- package/package.json +7 -7
- package/scripts/generate-sdk-adapter-parity-manifest.ts +5 -2
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/commands/sdk.ts +2 -2
- package/src/config/autorouting-tier-map.ts +0 -9
- package/src/config/file-lock.ts +15 -1
- package/src/config/keybindings.ts +13 -3
- package/src/config/model-registry.ts +132 -21
- package/src/config/model-resolver.ts +2 -1
- package/src/config/models-config-schema.ts +2 -2
- package/src/config/settings-schema.ts +13 -1
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +235 -4
- package/src/coordinator-mcp/server.ts +402 -2
- package/src/dap/client.ts +19 -8
- package/src/gjc-runtime/deep-interview-stage.ts +20 -1
- package/src/gjc-runtime/deep-interview-state.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/lsp/client.ts +39 -4
- package/src/main.ts +42 -11
- package/src/modes/action-registry.ts +7 -0
- package/src/modes/components/iterm-pet-transport.ts +4 -2
- package/src/modes/components/runtime-mcp-add-wizard.ts +38 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/tool-status-header.ts +149 -41
- package/src/modes/components/welcome.ts +8 -0
- package/src/modes/controllers/goal-mode-controller.ts +7 -2
- package/src/modes/controllers/input-controller.ts +141 -7
- package/src/modes/controllers/plan-mode-controller.ts +12 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +164 -20
- package/src/modes/controllers/selector-controller.ts +11 -2
- package/src/modes/interactive-mode.ts +88 -1
- package/src/modes/shared/oauth-url-copy.ts +33 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/ui-language.ts +44 -3
- package/src/runtime-mcp/oauth-flow.ts +19 -8
- package/src/runtime-mcp/transports/stdio.ts +27 -13
- package/src/sdk/acp/adapter.ts +29 -2
- package/src/sdk/broker/broker.ts +26 -6
- package/src/sdk/broker/lifecycle-ledger.ts +28 -1
- package/src/sdk/broker/lifecycle.ts +664 -5
- package/src/sdk/broker/session-index.ts +246 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/index.ts +135 -38
- package/src/sdk/bus/natives-augment.d.ts +20 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +3 -0
- package/src/sdk/cli/session-cli.ts +34 -7
- package/src/sdk/lifecycle/service.ts +166 -11
- package/src/sdk/mcp/server.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +40 -0
- package/src/sdk/protocol/operation-registry.ts +2 -1
- package/src/sdk/session.ts +91 -7
- package/src/session/agent-session.ts +403 -56
- package/src/session/fallback-chain-controller.ts +17 -0
- package/src/setup/frictionless-onboarding.ts +4 -4
- package/src/slash-commands/aside-command.test.ts +366 -0
- package/src/slash-commands/builtin-registry.ts +19 -0
- package/src/slash-commands/helpers/aside.ts +299 -0
- package/src/tools/ask.ts +8 -0
- package/src/utils/clipboard.ts +37 -28
|
@@ -273,6 +273,8 @@ export const ASK_CONTROLS_CAPABILITY = "ask_controls_v1";
|
|
|
273
273
|
export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
|
|
274
274
|
/** Receive-only compatibility capability for pre-v2 hosts. */
|
|
275
275
|
export const LEGACY_TOOL_ACTIVITY_CAPABILITY = "tool_activity_v1";
|
|
276
|
+
/** Opts notification adapters into positioned-only delivery for matching live effects. */
|
|
277
|
+
export const POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY = "positioned_notification_effects_v1";
|
|
276
278
|
type ToolActivityCapability = "v1" | "v2";
|
|
277
279
|
|
|
278
280
|
function negotiateToolActivityCapability(
|
|
@@ -5261,6 +5263,7 @@ export class TelegramNotificationDaemon {
|
|
|
5261
5263
|
ASK_SELECTED_ACK_CAPABILITY,
|
|
5262
5264
|
TOOL_ACTIVITY_CAPABILITY,
|
|
5263
5265
|
LEGACY_TOOL_ACTIVITY_CAPABILITY,
|
|
5266
|
+
POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY,
|
|
5264
5267
|
"ephemeral_turn_v1",
|
|
5265
5268
|
],
|
|
5266
5269
|
});
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
SessionLifecycleOperation,
|
|
12
12
|
SessionLifecycleSavedSession,
|
|
13
13
|
SessionLifecycleSavedSessionIdentity,
|
|
14
|
+
SessionReconcileUncertainTarget,
|
|
14
15
|
} from "../lifecycle/service";
|
|
15
16
|
import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../prompt-status";
|
|
16
17
|
import { validateAdapterControl, validateAdapterSecretFields } from "../protocol/adapter-validation";
|
|
@@ -36,6 +37,7 @@ export type SdkSessionCliAction =
|
|
|
36
37
|
| "send"
|
|
37
38
|
| "status"
|
|
38
39
|
| "tail"
|
|
40
|
+
| "retire"
|
|
39
41
|
| "raw"
|
|
40
42
|
| "control"
|
|
41
43
|
| "query"
|
|
@@ -209,7 +211,8 @@ function isLifecycleOperation(operation: string): operation is LifecycleMutation
|
|
|
209
211
|
operation === "session.fork" ||
|
|
210
212
|
operation === "session.resume" ||
|
|
211
213
|
operation === "session.close" ||
|
|
212
|
-
operation === "session.delete"
|
|
214
|
+
operation === "session.delete" ||
|
|
215
|
+
operation === "session.reconcile_uncertain"
|
|
213
216
|
);
|
|
214
217
|
}
|
|
215
218
|
|
|
@@ -1294,11 +1297,16 @@ function lifecycleMutationRequest(
|
|
|
1294
1297
|
2,
|
|
1295
1298
|
);
|
|
1296
1299
|
const target = input as JsonRecord & { sessionId: string };
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1300
|
+
if (operation === "session.resume") return { ...base, operation, capability: operation, target };
|
|
1301
|
+
if (operation === "session.close") return { ...base, operation, capability: operation, target };
|
|
1302
|
+
if (operation === "session.reconcile_uncertain")
|
|
1303
|
+
return {
|
|
1304
|
+
...base,
|
|
1305
|
+
operation,
|
|
1306
|
+
capability: operation,
|
|
1307
|
+
target: input as unknown as SessionReconcileUncertainTarget,
|
|
1308
|
+
};
|
|
1309
|
+
return { ...base, operation, capability: "session.delete", target };
|
|
1302
1310
|
}
|
|
1303
1311
|
|
|
1304
1312
|
async function runRawGlobal(
|
|
@@ -1346,6 +1354,7 @@ export async function runSdkSessionCli(
|
|
|
1346
1354
|
action !== "send" &&
|
|
1347
1355
|
action !== "status" &&
|
|
1348
1356
|
action !== "tail" &&
|
|
1357
|
+
action !== "retire" &&
|
|
1349
1358
|
action !== "raw" &&
|
|
1350
1359
|
action !== "control" &&
|
|
1351
1360
|
action !== "query" &&
|
|
@@ -1353,7 +1362,7 @@ export async function runSdkSessionCli(
|
|
|
1353
1362
|
)
|
|
1354
1363
|
throw new SdkSessionCliError(
|
|
1355
1364
|
"usage",
|
|
1356
|
-
"Expected one of: list, inspect, send, status, tail, raw (control|query|global).",
|
|
1365
|
+
"Expected one of: list, inspect, send, status, tail, retire, raw (control|query|global).",
|
|
1357
1366
|
2,
|
|
1358
1367
|
);
|
|
1359
1368
|
const agentDir = args.agentDir ?? getAgentDir();
|
|
@@ -1390,6 +1399,24 @@ export async function runSdkSessionCli(
|
|
|
1390
1399
|
);
|
|
1391
1400
|
return;
|
|
1392
1401
|
}
|
|
1402
|
+
if (action === "retire") {
|
|
1403
|
+
const sessionId = requireValue(args.sessionId, "<sessionId>");
|
|
1404
|
+
const input = await inputFromArgs(args);
|
|
1405
|
+
if (input.sessionId !== undefined && input.sessionId !== sessionId)
|
|
1406
|
+
throw new SdkSessionCliError(
|
|
1407
|
+
"invalid_input",
|
|
1408
|
+
"Retirement sessionId does not match the selected session.",
|
|
1409
|
+
2,
|
|
1410
|
+
);
|
|
1411
|
+
const secretError = validateAdapterSecretFields("session.reconcile_uncertain", input);
|
|
1412
|
+
if (secretError) throw new SdkSessionCliError(secretError.code, secretError.message, 2);
|
|
1413
|
+
writeOutput(
|
|
1414
|
+
stripSecretFields(
|
|
1415
|
+
await runRawGlobal(agentDir, "session.reconcile_uncertain", { ...input, sessionId }, args),
|
|
1416
|
+
),
|
|
1417
|
+
);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1393
1420
|
|
|
1394
1421
|
const kind = rawKind(requireValue(action, "<verb>"), args);
|
|
1395
1422
|
if (!kind) throw new SdkSessionCliError("usage", "raw requires one of: control, query, global.", 2);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import {
|
|
3
4
|
SessionListTraversalError,
|
|
4
5
|
type SessionListTraversalPage,
|
|
@@ -12,6 +13,7 @@ export type SessionLifecycleOperation =
|
|
|
12
13
|
| "session.resume"
|
|
13
14
|
| "session.close"
|
|
14
15
|
| "session.delete"
|
|
16
|
+
| "session.reconcile_uncertain"
|
|
15
17
|
| "session.list";
|
|
16
18
|
|
|
17
19
|
export interface SessionLifecycleActor {
|
|
@@ -117,6 +119,18 @@ export interface SessionCloseTarget {
|
|
|
117
119
|
readonly endpointIncarnation?: string;
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
export interface SessionReconcileUncertainTarget {
|
|
123
|
+
readonly sessionId: string;
|
|
124
|
+
readonly cwd: string;
|
|
125
|
+
readonly stateRoot: string;
|
|
126
|
+
readonly endpointGeneration: number;
|
|
127
|
+
readonly endpointMtimeMs: number;
|
|
128
|
+
readonly processIncarnation: string;
|
|
129
|
+
readonly hostIncarnation: string;
|
|
130
|
+
readonly lifecycleRequestId: string;
|
|
131
|
+
readonly remoteCreateKey: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
120
134
|
export interface SessionDeleteTarget {
|
|
121
135
|
readonly sessionId: string;
|
|
122
136
|
readonly cwd?: string;
|
|
@@ -146,6 +160,10 @@ export type SessionForkRequest = SessionLifecycleMutationRequestBase<"session.fo
|
|
|
146
160
|
export type SessionResumeRequest = SessionLifecycleMutationRequestBase<"session.resume", SessionResumeTarget>;
|
|
147
161
|
export type SessionCloseRequest = SessionLifecycleMutationRequestBase<"session.close", SessionCloseTarget>;
|
|
148
162
|
export type SessionDeleteRequest = SessionLifecycleMutationRequestBase<"session.delete", SessionDeleteTarget>;
|
|
163
|
+
export type SessionReconcileUncertainRequest = SessionLifecycleMutationRequestBase<
|
|
164
|
+
"session.reconcile_uncertain",
|
|
165
|
+
SessionReconcileUncertainTarget
|
|
166
|
+
>;
|
|
149
167
|
export interface SessionListRequest {
|
|
150
168
|
readonly operation: "session.list";
|
|
151
169
|
readonly actor: SessionLifecycleActor;
|
|
@@ -161,7 +179,8 @@ export type SessionLifecycleMutationRequest =
|
|
|
161
179
|
| SessionForkRequest
|
|
162
180
|
| SessionResumeRequest
|
|
163
181
|
| SessionCloseRequest
|
|
164
|
-
| SessionDeleteRequest
|
|
182
|
+
| SessionDeleteRequest
|
|
183
|
+
| SessionReconcileUncertainRequest;
|
|
165
184
|
|
|
166
185
|
export type SessionLifecycleCertainty = "terminal" | "retryable" | "cleanup_pending" | "uncertain";
|
|
167
186
|
|
|
@@ -213,6 +232,11 @@ export interface SessionDeleteResult {
|
|
|
213
232
|
readonly operation: "session.delete";
|
|
214
233
|
readonly result: SessionLifecycleSessionResult;
|
|
215
234
|
}
|
|
235
|
+
export interface SessionReconcileUncertainResult {
|
|
236
|
+
readonly ok: true;
|
|
237
|
+
readonly operation: "session.reconcile_uncertain";
|
|
238
|
+
readonly result: SessionLifecycleSessionResult;
|
|
239
|
+
}
|
|
216
240
|
export interface SessionListSuccessResult {
|
|
217
241
|
readonly ok: true;
|
|
218
242
|
readonly operation: "session.list";
|
|
@@ -254,6 +278,12 @@ export type SessionDeleteFailure = {
|
|
|
254
278
|
readonly certainty: SessionLifecycleCertainty;
|
|
255
279
|
readonly error: SessionLifecycleError;
|
|
256
280
|
};
|
|
281
|
+
export type SessionReconcileUncertainFailure = {
|
|
282
|
+
readonly ok: false;
|
|
283
|
+
readonly operation: "session.reconcile_uncertain";
|
|
284
|
+
readonly certainty: SessionLifecycleCertainty;
|
|
285
|
+
readonly error: SessionLifecycleError;
|
|
286
|
+
};
|
|
257
287
|
export type SessionListFailure = {
|
|
258
288
|
readonly ok: false;
|
|
259
289
|
readonly operation: "session.list";
|
|
@@ -266,6 +296,7 @@ export type SessionForkOutcome = SessionForkResult | SessionForkFailure;
|
|
|
266
296
|
export type SessionResumeOutcome = SessionResumeResult | SessionResumeFailure;
|
|
267
297
|
export type SessionCloseOutcome = SessionCloseResult | SessionCloseFailure;
|
|
268
298
|
export type SessionDeleteOutcome = SessionDeleteResult | SessionDeleteFailure;
|
|
299
|
+
export type SessionReconcileUncertainOutcome = SessionReconcileUncertainResult | SessionReconcileUncertainFailure;
|
|
269
300
|
export type SessionListOutcome = SessionListSuccessResult | SessionListFailure;
|
|
270
301
|
export type SessionLifecycleResult =
|
|
271
302
|
| SessionCreateOutcome
|
|
@@ -273,6 +304,7 @@ export type SessionLifecycleResult =
|
|
|
273
304
|
| SessionResumeOutcome
|
|
274
305
|
| SessionCloseOutcome
|
|
275
306
|
| SessionDeleteOutcome
|
|
307
|
+
| SessionReconcileUncertainOutcome
|
|
276
308
|
| SessionListOutcome;
|
|
277
309
|
|
|
278
310
|
/** Shared, side-effect-free validation for lifecycle mutation requests. */
|
|
@@ -288,7 +320,8 @@ export type SessionLifecycleMutationValidation =
|
|
|
288
320
|
| SessionForkFailure
|
|
289
321
|
| SessionResumeFailure
|
|
290
322
|
| SessionCloseFailure
|
|
291
|
-
| SessionDeleteFailure
|
|
323
|
+
| SessionDeleteFailure
|
|
324
|
+
| SessionReconcileUncertainFailure;
|
|
292
325
|
|
|
293
326
|
const RETRYABLE_BROKER_ERRORS = new Set([
|
|
294
327
|
"unavailable",
|
|
@@ -338,6 +371,7 @@ function operationOf(value: unknown): SessionLifecycleOperation {
|
|
|
338
371
|
value === "session.resume" ||
|
|
339
372
|
value === "session.close" ||
|
|
340
373
|
value === "session.delete" ||
|
|
374
|
+
value === "session.reconcile_uncertain" ||
|
|
341
375
|
value === "session.list"
|
|
342
376
|
)
|
|
343
377
|
return value;
|
|
@@ -376,6 +410,33 @@ function validTarget(target: unknown): target is Readonly<Record<string, unknown
|
|
|
376
410
|
return isRecord(target);
|
|
377
411
|
}
|
|
378
412
|
|
|
413
|
+
export function validateSessionReconcileUncertainTarget(value: unknown): value is SessionReconcileUncertainTarget {
|
|
414
|
+
if (!isRecord(value)) return false;
|
|
415
|
+
const target = value;
|
|
416
|
+
const bounded = (value: unknown, max: number): value is string =>
|
|
417
|
+
typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
418
|
+
return (
|
|
419
|
+
bounded(target.sessionId, 256) &&
|
|
420
|
+
/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(target.sessionId) &&
|
|
421
|
+
bounded(target.cwd, 4096) &&
|
|
422
|
+
path.isAbsolute(target.cwd) &&
|
|
423
|
+
bounded(target.stateRoot, 4096) &&
|
|
424
|
+
path.isAbsolute(target.stateRoot) &&
|
|
425
|
+
path.resolve(target.stateRoot) === path.join(path.resolve(target.cwd), ".gjc", "state") &&
|
|
426
|
+
typeof target.endpointGeneration === "number" &&
|
|
427
|
+
Number.isSafeInteger(target.endpointGeneration) &&
|
|
428
|
+
target.endpointGeneration > 0 &&
|
|
429
|
+
typeof target.endpointMtimeMs === "number" &&
|
|
430
|
+
Number.isFinite(target.endpointMtimeMs) &&
|
|
431
|
+
target.endpointMtimeMs > 0 &&
|
|
432
|
+
bounded(target.processIncarnation, 256) &&
|
|
433
|
+
bounded(target.hostIncarnation, 256) &&
|
|
434
|
+
bounded(target.lifecycleRequestId, 128) &&
|
|
435
|
+
/^[A-Za-z0-9._-]+$/u.test(target.lifecycleRequestId) &&
|
|
436
|
+
bounded(target.remoteCreateKey, 256)
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
379
440
|
/** Validates lifecycle authority and shape without contacting the Broker. */
|
|
380
441
|
export function validateSessionLifecycleMutationRequest(request: unknown): SessionLifecycleMutationValidation {
|
|
381
442
|
const record = isRecord(request) ? request : {};
|
|
@@ -390,6 +451,13 @@ export function validateSessionLifecycleMutationRequest(request: unknown): Sessi
|
|
|
390
451
|
return failure(operation, "terminal", "capability_denied", `capability does not authorize ${operation}`);
|
|
391
452
|
if (!validTarget(record.target))
|
|
392
453
|
return failure(operation, "terminal", "invalid_request", "target must be an object");
|
|
454
|
+
if (operation === "session.reconcile_uncertain" && !validateSessionReconcileUncertainTarget(record.target))
|
|
455
|
+
return failure(
|
|
456
|
+
operation,
|
|
457
|
+
"terminal",
|
|
458
|
+
"invalid_request",
|
|
459
|
+
"session.reconcile_uncertain target must carry complete identity-bound retirement proof",
|
|
460
|
+
);
|
|
393
461
|
return {
|
|
394
462
|
ok: true,
|
|
395
463
|
operation,
|
|
@@ -439,6 +507,28 @@ function sessionResult(value: unknown, expectedSessionId?: string): SessionLifec
|
|
|
439
507
|
return result;
|
|
440
508
|
}
|
|
441
509
|
|
|
510
|
+
function reconcileUncertainResult(
|
|
511
|
+
value: unknown,
|
|
512
|
+
target: SessionReconcileUncertainTarget,
|
|
513
|
+
): SessionLifecycleSessionResult | undefined {
|
|
514
|
+
if (!isRecord(value)) return undefined;
|
|
515
|
+
if (
|
|
516
|
+
value.sessionId !== target.sessionId ||
|
|
517
|
+
value.retired !== true ||
|
|
518
|
+
value.ledgerState !== "terminal_error" ||
|
|
519
|
+
value.indexType !== "session_closed" ||
|
|
520
|
+
value.stateRoot !== target.stateRoot ||
|
|
521
|
+
value.endpointGeneration !== target.endpointGeneration ||
|
|
522
|
+
value.endpointMtimeMs !== target.endpointMtimeMs ||
|
|
523
|
+
value.processIncarnation !== target.processIncarnation ||
|
|
524
|
+
value.hostIncarnation !== target.hostIncarnation ||
|
|
525
|
+
value.lifecycleRequestId !== target.lifecycleRequestId ||
|
|
526
|
+
value.remoteCreateKey !== target.remoteCreateKey
|
|
527
|
+
)
|
|
528
|
+
return undefined;
|
|
529
|
+
return { sessionId: target.sessionId, endpointGeneration: target.endpointGeneration };
|
|
530
|
+
}
|
|
531
|
+
|
|
442
532
|
function savedSessionTranscriptIdentity(value: unknown): SessionLifecycleSavedSessionIdentity | undefined {
|
|
443
533
|
if (!isRecord(value)) return undefined;
|
|
444
534
|
const { dev, ino, nlink, size, mtimeMs, mtimeNs, ctimeNs, sha256 } = value;
|
|
@@ -588,20 +678,39 @@ export class SessionLifecycleService {
|
|
|
588
678
|
this.#client = client;
|
|
589
679
|
}
|
|
590
680
|
|
|
591
|
-
async execute(
|
|
681
|
+
async #execute(
|
|
592
682
|
request: SessionLifecycleMutationRequest,
|
|
683
|
+
idempotencyKeyOverride?: string,
|
|
593
684
|
): Promise<
|
|
594
|
-
|
|
685
|
+
| SessionCreateOutcome
|
|
686
|
+
| SessionForkOutcome
|
|
687
|
+
| SessionResumeOutcome
|
|
688
|
+
| SessionCloseOutcome
|
|
689
|
+
| SessionDeleteOutcome
|
|
690
|
+
| SessionReconcileUncertainOutcome
|
|
595
691
|
> {
|
|
596
692
|
const validation = validateSessionLifecycleMutationRequest(request);
|
|
597
693
|
if (!validation.ok) return validation;
|
|
598
694
|
const { operation, actor, requestKey, target } = validation;
|
|
599
|
-
const
|
|
695
|
+
const normalizedTarget =
|
|
696
|
+
operation === "session.reconcile_uncertain"
|
|
697
|
+
? {
|
|
698
|
+
...target,
|
|
699
|
+
cwd: path.resolve((target as unknown as SessionReconcileUncertainTarget).cwd),
|
|
700
|
+
stateRoot: path.join(
|
|
701
|
+
path.resolve((target as unknown as SessionReconcileUncertainTarget).cwd),
|
|
702
|
+
".gjc",
|
|
703
|
+
"state",
|
|
704
|
+
),
|
|
705
|
+
}
|
|
706
|
+
: target;
|
|
707
|
+
const idempotencyKey =
|
|
708
|
+
idempotencyKeyOverride ?? deriveSessionLifecycleIdempotencyKey(actor, requestKey, operation);
|
|
600
709
|
let response: unknown;
|
|
601
710
|
try {
|
|
602
711
|
response = await this.#client.global(
|
|
603
712
|
operation,
|
|
604
|
-
{ ...
|
|
713
|
+
{ ...normalizedTarget },
|
|
605
714
|
{
|
|
606
715
|
idempotencyKey,
|
|
607
716
|
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
@@ -619,12 +728,21 @@ export class SessionLifecycleService {
|
|
|
619
728
|
if (!isRecord(response) || response.ok !== true)
|
|
620
729
|
return failure(operation, "uncertain", "malformed_response", "lifecycle broker returned a malformed response");
|
|
621
730
|
const expectedSessionId =
|
|
622
|
-
operation === "session.resume" ||
|
|
623
|
-
|
|
624
|
-
|
|
731
|
+
operation === "session.resume" ||
|
|
732
|
+
operation === "session.close" ||
|
|
733
|
+
operation === "session.delete" ||
|
|
734
|
+
operation === "session.reconcile_uncertain"
|
|
735
|
+
? typeof normalizedTarget.sessionId === "string"
|
|
736
|
+
? normalizedTarget.sessionId
|
|
625
737
|
: undefined
|
|
626
738
|
: undefined;
|
|
627
|
-
const parsed =
|
|
739
|
+
const parsed =
|
|
740
|
+
operation === "session.reconcile_uncertain"
|
|
741
|
+
? reconcileUncertainResult(
|
|
742
|
+
brokerSuccess(response),
|
|
743
|
+
normalizedTarget as unknown as SessionReconcileUncertainTarget,
|
|
744
|
+
)
|
|
745
|
+
: sessionResult(brokerSuccess(response), expectedSessionId);
|
|
628
746
|
if (!parsed)
|
|
629
747
|
return failure(
|
|
630
748
|
operation,
|
|
@@ -637,7 +755,35 @@ export class SessionLifecycleService {
|
|
|
637
755
|
| SessionForkOutcome
|
|
638
756
|
| SessionResumeOutcome
|
|
639
757
|
| SessionCloseOutcome
|
|
640
|
-
| SessionDeleteOutcome
|
|
758
|
+
| SessionDeleteOutcome
|
|
759
|
+
| SessionReconcileUncertainOutcome;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async execute(
|
|
763
|
+
request: SessionLifecycleMutationRequest,
|
|
764
|
+
): Promise<
|
|
765
|
+
| SessionCreateOutcome
|
|
766
|
+
| SessionForkOutcome
|
|
767
|
+
| SessionResumeOutcome
|
|
768
|
+
| SessionCloseOutcome
|
|
769
|
+
| SessionDeleteOutcome
|
|
770
|
+
| SessionReconcileUncertainOutcome
|
|
771
|
+
> {
|
|
772
|
+
return this.#execute(request);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async executeWithIdempotencyKey(
|
|
776
|
+
request: SessionLifecycleMutationRequest,
|
|
777
|
+
idempotencyKey: string,
|
|
778
|
+
): Promise<
|
|
779
|
+
| SessionCreateOutcome
|
|
780
|
+
| SessionForkOutcome
|
|
781
|
+
| SessionResumeOutcome
|
|
782
|
+
| SessionCloseOutcome
|
|
783
|
+
| SessionDeleteOutcome
|
|
784
|
+
| SessionReconcileUncertainOutcome
|
|
785
|
+
> {
|
|
786
|
+
return this.#execute(request, idempotencyKey);
|
|
641
787
|
}
|
|
642
788
|
|
|
643
789
|
async create(request: Omit<SessionCreateRequest, "operation">): Promise<SessionCreateOutcome> {
|
|
@@ -660,6 +806,15 @@ export class SessionLifecycleService {
|
|
|
660
806
|
return (await this.execute({ ...request, operation: "session.delete" })) as SessionDeleteOutcome;
|
|
661
807
|
}
|
|
662
808
|
|
|
809
|
+
async reconcileUncertain(
|
|
810
|
+
request: Omit<SessionReconcileUncertainRequest, "operation">,
|
|
811
|
+
): Promise<SessionReconcileUncertainOutcome> {
|
|
812
|
+
return (await this.execute({
|
|
813
|
+
...request,
|
|
814
|
+
operation: "session.reconcile_uncertain",
|
|
815
|
+
})) as SessionReconcileUncertainOutcome;
|
|
816
|
+
}
|
|
817
|
+
|
|
663
818
|
async list(request: Omit<SessionListRequest, "operation">): Promise<SessionListOutcome> {
|
|
664
819
|
if (!validActor((request as { actor?: unknown }).actor))
|
|
665
820
|
return failure("session.list", "terminal", "unauthorized", "authenticated actor is required");
|
package/src/sdk/mcp/server.ts
CHANGED
|
@@ -158,7 +158,8 @@ function isLifecycleOperation(operation: string): operation is LifecycleMutation
|
|
|
158
158
|
operation === "session.fork" ||
|
|
159
159
|
operation === "session.resume" ||
|
|
160
160
|
operation === "session.close" ||
|
|
161
|
-
operation === "session.delete"
|
|
161
|
+
operation === "session.delete" ||
|
|
162
|
+
operation === "session.reconcile_uncertain"
|
|
162
163
|
);
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -1079,6 +1079,24 @@
|
|
|
1079
1079
|
"packages/coding-agent/test/sdk-operation-inventory.test.ts"
|
|
1080
1080
|
]
|
|
1081
1081
|
},
|
|
1082
|
+
{
|
|
1083
|
+
"sourceId": "registry:G08",
|
|
1084
|
+
"sourceFile": "packages/coding-agent/src/sdk/protocol/operation-registry.ts",
|
|
1085
|
+
"sourceKind": "registry",
|
|
1086
|
+
"decision": "include",
|
|
1087
|
+
"sdkId": "session.reconcile_uncertain",
|
|
1088
|
+
"adapterMappings": {
|
|
1089
|
+
"telegram": "prohibited",
|
|
1090
|
+
"discord": "prohibited",
|
|
1091
|
+
"slack": "prohibited",
|
|
1092
|
+
"mcp": "generic_safe",
|
|
1093
|
+
"acp": "generic_safe",
|
|
1094
|
+
"daemonCli": "generic_safe"
|
|
1095
|
+
},
|
|
1096
|
+
"testIds": [
|
|
1097
|
+
"packages/coding-agent/test/sdk-operation-inventory.test.ts"
|
|
1098
|
+
]
|
|
1099
|
+
},
|
|
1082
1100
|
{
|
|
1083
1101
|
"sourceId": "registry:Q01",
|
|
1084
1102
|
"sourceFile": "packages/coding-agent/src/sdk/protocol/operation-registry.ts",
|
|
@@ -1727,6 +1745,17 @@
|
|
|
1727
1745
|
"packages/coding-agent/test/sdk-operation-inventory.test.ts"
|
|
1728
1746
|
]
|
|
1729
1747
|
},
|
|
1748
|
+
{
|
|
1749
|
+
"sourceId": "slash_command:aside",
|
|
1750
|
+
"sourceFile": "packages/coding-agent/src/slash-commands/builtin-registry.ts",
|
|
1751
|
+
"sourceKind": "slash_command",
|
|
1752
|
+
"decision": "exclude",
|
|
1753
|
+
"rationale": "explicit user-owned external CLI process launcher; not a credential-free SDK control seam",
|
|
1754
|
+
"exclusionMetadata": {
|
|
1755
|
+
"adapterMappings": "not_applicable",
|
|
1756
|
+
"testIds": "not_applicable"
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1730
1759
|
{
|
|
1731
1760
|
"sourceId": "slash_command:import-session",
|
|
1732
1761
|
"sourceFile": "packages/coding-agent/src/slash-commands/builtin-registry.ts",
|
|
@@ -2874,6 +2903,17 @@
|
|
|
2874
2903
|
"packages/coding-agent/test/sdk-operation-inventory.test.ts"
|
|
2875
2904
|
]
|
|
2876
2905
|
},
|
|
2906
|
+
{
|
|
2907
|
+
"sourceId": "agent_session:isExplicitEmptyToolSelection",
|
|
2908
|
+
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
2909
|
+
"sourceKind": "agent_session",
|
|
2910
|
+
"decision": "exclude",
|
|
2911
|
+
"rationale": "internal tool-selection predicate used to preserve the --no-tools boundary, not a user-facing SDK control seam",
|
|
2912
|
+
"exclusionMetadata": {
|
|
2913
|
+
"adapterMappings": "not_applicable",
|
|
2914
|
+
"testIds": "not_applicable"
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2877
2917
|
{
|
|
2878
2918
|
"sourceId": "agent_session:getToolByName",
|
|
2879
2919
|
"sourceFile": "packages/coding-agent/src/session/agent-session.ts",
|
|
@@ -135,6 +135,7 @@ const globals = [
|
|
|
135
135
|
["session.resume", "Resume a saved session."],
|
|
136
136
|
["session.close", "Close a saved session."],
|
|
137
137
|
["session.delete", "Delete a saved session."],
|
|
138
|
+
["session.reconcile_uncertain", "Retire an indexed terminalUncertain create effect after dead-host proof."],
|
|
138
139
|
] as const;
|
|
139
140
|
|
|
140
141
|
const queries = [
|
|
@@ -349,7 +350,7 @@ export const OPERATIONS: readonly Operation[] = [
|
|
|
349
350
|
acp: "machine_only",
|
|
350
351
|
daemonCli: "machine_only",
|
|
351
352
|
})
|
|
352
|
-
: ["G03", "G04", "G05", "G06", "G07"].includes(id)
|
|
353
|
+
: ["G03", "G04", "G05", "G06", "G07", "G08"].includes(id)
|
|
353
354
|
? dispositions({ telegram: "prohibited", discord: "prohibited", slack: "prohibited" })
|
|
354
355
|
: dispositions(),
|
|
355
356
|
testIds: ["packages/coding-agent/test/sdk-operation-inventory.test.ts"],
|
package/src/sdk/session.ts
CHANGED
|
@@ -59,6 +59,7 @@ import { kNoAuth, ModelRegistry } from "../config/model-registry";
|
|
|
59
59
|
import {
|
|
60
60
|
formatModelString,
|
|
61
61
|
parseModelPattern,
|
|
62
|
+
parseModelString,
|
|
62
63
|
resolveAllowedModels,
|
|
63
64
|
resolveModelChainWithAuth,
|
|
64
65
|
resolveModelRoleValue,
|
|
@@ -430,6 +431,8 @@ export interface CreateAgentSessionOptions {
|
|
|
430
431
|
authStorage?: AuthStorage;
|
|
431
432
|
/** Model registry. Default: discoverModels(authStorage, agentDir) */
|
|
432
433
|
modelRegistry?: ModelRegistry;
|
|
434
|
+
/** @internal Allows the first-party CLI root to mutate its own registry and observe an admission attempt. */
|
|
435
|
+
modelRegistryStartupMutation?: { owner: "cli-root"; onAttempt(): void };
|
|
433
436
|
|
|
434
437
|
/** Model to use. Default: from settings, else first available */
|
|
435
438
|
model?: Model;
|
|
@@ -1442,7 +1445,45 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1442
1445
|
const runtimeServices = createOptionalRuntimeServices(settings, options.runtimeServices, { cwd: getLiveCwd });
|
|
1443
1446
|
modelRegistry.applyConfiguredModelBindings(settings);
|
|
1444
1447
|
logger.time("initializeWithSettings", initializeWithSettings, settings);
|
|
1445
|
-
|
|
1448
|
+
const startupModelReference =
|
|
1449
|
+
options.model === undefined
|
|
1450
|
+
? options.modelPattern
|
|
1451
|
+
? parseModelString(options.modelPattern)
|
|
1452
|
+
: undefined
|
|
1453
|
+
: options.modelRegistryStartupMutation?.owner === "cli-root"
|
|
1454
|
+
? { provider: options.model.provider, id: options.model.id }
|
|
1455
|
+
: undefined;
|
|
1456
|
+
const startupRegistryMutationAuthorized =
|
|
1457
|
+
options.modelRegistry === undefined || options.modelRegistryStartupMutation?.owner === "cli-root";
|
|
1458
|
+
const startupCredentialSelector =
|
|
1459
|
+
startupModelReference &&
|
|
1460
|
+
options.credentialSelector &&
|
|
1461
|
+
(!options.credentialSelector.provider ||
|
|
1462
|
+
options.credentialSelector.provider.toLowerCase() === startupModelReference.provider.toLowerCase())
|
|
1463
|
+
? options.credentialSelector.selector
|
|
1464
|
+
: undefined;
|
|
1465
|
+
const startupCredentialProviderMismatch =
|
|
1466
|
+
startupModelReference !== undefined &&
|
|
1467
|
+
options.credentialSelector?.provider !== undefined &&
|
|
1468
|
+
options.credentialSelector.provider.toLowerCase() !== startupModelReference.provider.toLowerCase();
|
|
1469
|
+
const attemptedStartupCacheAdmission =
|
|
1470
|
+
startupModelReference !== undefined &&
|
|
1471
|
+
startupRegistryMutationAuthorized &&
|
|
1472
|
+
options.credentialSelector !== undefined &&
|
|
1473
|
+
modelRegistry.requiresStoredLiteralCredentialCacheAdmission(
|
|
1474
|
+
startupModelReference.provider,
|
|
1475
|
+
startupModelReference.id,
|
|
1476
|
+
);
|
|
1477
|
+
if (attemptedStartupCacheAdmission) options.modelRegistryStartupMutation?.onAttempt();
|
|
1478
|
+
if (startupModelReference && startupCredentialSelector) {
|
|
1479
|
+
if (attemptedStartupCacheAdmission) {
|
|
1480
|
+
modelRegistry.admitCachedProviderForStoredLiteralCredential(
|
|
1481
|
+
startupModelReference.provider,
|
|
1482
|
+
startupCredentialSelector,
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
if (!options.modelRegistry && !attemptedStartupCacheAdmission) {
|
|
1446
1487
|
modelRegistry.refreshInBackground();
|
|
1447
1488
|
}
|
|
1448
1489
|
// Resolve the workspace tree through its runtime service. The compatibility
|
|
@@ -3254,8 +3295,41 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3254
3295
|
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
3255
3296
|
}
|
|
3256
3297
|
|
|
3298
|
+
let startupCredentialModelRejected = false;
|
|
3299
|
+
if (startupModelReference && (startupCredentialSelector || startupCredentialProviderMismatch)) {
|
|
3300
|
+
const validated =
|
|
3301
|
+
!startupCredentialProviderMismatch &&
|
|
3302
|
+
startupCredentialSelector !== undefined &&
|
|
3303
|
+
modelRegistry.validateModelForStoredLiteralCredential(
|
|
3304
|
+
startupModelReference.provider,
|
|
3305
|
+
startupModelReference.id,
|
|
3306
|
+
startupCredentialSelector,
|
|
3307
|
+
);
|
|
3308
|
+
const currentStartupModel = modelRegistry.find(startupModelReference.provider, startupModelReference.id);
|
|
3309
|
+
const providerMismatchRejectsTarget =
|
|
3310
|
+
startupCredentialProviderMismatch &&
|
|
3311
|
+
modelRegistry.requiresStoredLiteralCredentialCacheAdmission(
|
|
3312
|
+
startupModelReference.provider,
|
|
3313
|
+
startupModelReference.id,
|
|
3314
|
+
);
|
|
3315
|
+
if (
|
|
3316
|
+
(!validated && (startupCredentialSelector !== undefined || providerMismatchRejectsTarget)) ||
|
|
3317
|
+
!currentStartupModel
|
|
3318
|
+
) {
|
|
3319
|
+
startupCredentialModelRejected = true;
|
|
3320
|
+
if (model?.provider === startupModelReference.provider && model.id === startupModelReference.id) {
|
|
3321
|
+
model = undefined;
|
|
3322
|
+
}
|
|
3323
|
+
modelFallbackMessage = `Model "${
|
|
3324
|
+
options.modelPattern ?? `${startupModelReference.provider}/${startupModelReference.id}`
|
|
3325
|
+
}" not found`;
|
|
3326
|
+
} else if (model?.provider === startupModelReference.provider && model.id === startupModelReference.id) {
|
|
3327
|
+
model = currentStartupModel;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3257
3331
|
// Resolve deferred --model pattern now that extension models are registered.
|
|
3258
|
-
if (!model && options.modelPattern) {
|
|
3332
|
+
if (!model && options.modelPattern && !startupCredentialModelRejected) {
|
|
3259
3333
|
const availableModels = modelRegistry.getAll();
|
|
3260
3334
|
const matchPreferences = {
|
|
3261
3335
|
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
@@ -3290,7 +3364,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3290
3364
|
// Fall back to first available model with a valid API key, honoring the
|
|
3291
3365
|
// path-scoped `enabledModels` allow-list when configured. Skip when the
|
|
3292
3366
|
// user explicitly requested a model via --model that wasn't found.
|
|
3293
|
-
if (!model && !options.modelPattern) {
|
|
3367
|
+
if (!model && !options.modelPattern && !startupCredentialModelRejected) {
|
|
3294
3368
|
// Re-resolve the allowed set: extension factories above may have
|
|
3295
3369
|
// registered providers/models that weren't visible at startup.
|
|
3296
3370
|
const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
@@ -3660,11 +3734,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3660
3734
|
|
|
3661
3735
|
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
3662
3736
|
const hasExplicitToolNames = options.toolNames !== undefined;
|
|
3737
|
+
const hasExplicitEmptyToolSelection = hasExplicitToolNames && options.toolNames!.length === 0;
|
|
3738
|
+
// `goal` is session state rather than a work tool, so it is folded into an
|
|
3739
|
+
// explicit selection that already asks for tools. An *empty* explicit
|
|
3740
|
+
// selection is the `--no-tools` contract ("Disable all built-in tools", see
|
|
3741
|
+
// cli/root-flags.ts), and re-populating it with `goal` would leave exactly one
|
|
3742
|
+
// built-in tool live against that promise.
|
|
3743
|
+
const wantsGoalStateTool = hasExplicitToolNames && options.toolNames!.length > 0;
|
|
3663
3744
|
const requestedToolNames = hasExplicitToolNames
|
|
3664
3745
|
? [
|
|
3665
3746
|
...new Set([
|
|
3666
3747
|
...options.toolNames!.map(name => name.toLowerCase()),
|
|
3667
|
-
...(settings.get("goal.enabled") ? ["goal"] : []),
|
|
3748
|
+
...(wantsGoalStateTool && settings.get("goal.enabled") ? ["goal"] : []),
|
|
3668
3749
|
]),
|
|
3669
3750
|
]
|
|
3670
3751
|
: toolNamesFromRegistry;
|
|
@@ -3804,9 +3885,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3804
3885
|
allowedDiscoveredBuiltinNames,
|
|
3805
3886
|
essentialBuiltinNames,
|
|
3806
3887
|
);
|
|
3807
|
-
initialSelectedDiscoveredBuiltinToolNames =
|
|
3808
|
-
?
|
|
3809
|
-
:
|
|
3888
|
+
initialSelectedDiscoveredBuiltinToolNames = hasExplicitEmptyToolSelection
|
|
3889
|
+
? []
|
|
3890
|
+
: existingSession.hasPersistedDiscoveredBuiltinToolSelection
|
|
3891
|
+
? restoredDiscoveredNames
|
|
3892
|
+
: explicitlyRequestedDiscoveredBuiltinToolNames;
|
|
3810
3893
|
initialToolNames = [...new Set([...baselineInitialToolNames, ...initialSelectedDiscoveredBuiltinToolNames])];
|
|
3811
3894
|
hasExplicitDiscoveredBuiltinToolSelection =
|
|
3812
3895
|
hasExplicitToolNames &&
|
|
@@ -4163,6 +4246,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4163
4246
|
},
|
|
4164
4247
|
reloadSshTool,
|
|
4165
4248
|
requestedToolNames: requestedToolNameSet,
|
|
4249
|
+
explicitEmptyToolSelection: hasExplicitEmptyToolSelection,
|
|
4166
4250
|
discoverableToolAllowedNames: options.discoverableToolAllowedNames,
|
|
4167
4251
|
mcpDiscoveryEnabled,
|
|
4168
4252
|
discoveryMode: effectiveDiscoveryMode,
|