@gajae-code/coding-agent 0.12.7 → 0.12.8
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 +19 -0
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/session/session-storage.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +97 -35
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +44 -24
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +382 -45
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/session-manager.ts +27 -3
- package/src/session/session-storage.ts +72 -5
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +9 -3
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/telegram-send.ts +4 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const SESSION_MIGRATION_BUSY_MESSAGE =
|
|
2
|
+
"Another session migration is still active. Wait for it to finish, then retry.";
|
|
3
|
+
|
|
4
|
+
export class SessionMigrationBusyError extends Error {
|
|
5
|
+
readonly code = "migration_busy";
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
super(SESSION_MIGRATION_BUSY_MESSAGE);
|
|
9
|
+
this.name = "SessionMigrationBusyError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -83,8 +83,8 @@ import {
|
|
|
83
83
|
mayCleanManagedTreeStaging,
|
|
84
84
|
retainManagedDirectoryAuthority,
|
|
85
85
|
} from "./internal/managed-session-storage";
|
|
86
|
-
|
|
87
86
|
import { classifyNativePublishOutcome, formatNativePublishDiagnostic } from "./internal/native-publish-outcome";
|
|
87
|
+
import { SessionMigrationBusyError } from "./internal/session-open-errors";
|
|
88
88
|
import {
|
|
89
89
|
hasOnlyKeys as hasOnlyMemoryGuardKeys,
|
|
90
90
|
isMemoryGuardDecimalString,
|
|
@@ -940,6 +940,8 @@ export class SessionMigrationPolicyError extends Error {
|
|
|
940
940
|
}
|
|
941
941
|
|
|
942
942
|
export class SessionArtifactCapacityError extends Error {
|
|
943
|
+
readonly code = "artifact_capacity_exceeded";
|
|
944
|
+
|
|
943
945
|
constructor(message: string) {
|
|
944
946
|
super(message);
|
|
945
947
|
this.name = "SessionArtifactCapacityError";
|
|
@@ -954,7 +956,12 @@ export interface StrictSessionOpenSuccess {
|
|
|
954
956
|
|
|
955
957
|
export interface StrictSessionOpenFailure {
|
|
956
958
|
kind: "error";
|
|
957
|
-
reason:
|
|
959
|
+
reason:
|
|
960
|
+
| ResumeTailError["reason"]
|
|
961
|
+
| "identity-mismatch"
|
|
962
|
+
| "migration-required"
|
|
963
|
+
| "artifact_capacity_exceeded"
|
|
964
|
+
| "migration_busy";
|
|
958
965
|
message?: string;
|
|
959
966
|
}
|
|
960
967
|
|
|
@@ -4707,6 +4714,7 @@ export const MATERIALIZED_CACHE_MAX_BYTES = 32 * 1024 * 1024;
|
|
|
4707
4714
|
export const SessionManagerTestHooks: {
|
|
4708
4715
|
materializedCacheMaxBytesOverride?: number;
|
|
4709
4716
|
beforeResidentTransitionIndexBuild?: () => void;
|
|
4717
|
+
afterForkSnapshot?: () => void | Promise<void>;
|
|
4710
4718
|
} = {};
|
|
4711
4719
|
|
|
4712
4720
|
function materializedCacheMaxBytes(): number {
|
|
@@ -6165,6 +6173,7 @@ export class SessionManager {
|
|
|
6165
6173
|
const candidate = listing.owned.find(candidate => path.resolve(candidate.path) === requestedPath);
|
|
6166
6174
|
if (!candidate) throw new Error("Managed session deletion requires exact logical authorization.");
|
|
6167
6175
|
const deleted = await deleteManagedSessionCandidate(resolved.scope, candidate);
|
|
6176
|
+
if (deleted.kind === "error" && deleted.code === "migration_busy") throw new SessionMigrationBusyError();
|
|
6168
6177
|
if (deleted.kind !== "deleted" && deleted.kind !== "already_deleted")
|
|
6169
6178
|
throw new Error(`Could not delete managed session: ${deleted.message}`);
|
|
6170
6179
|
} else {
|
|
@@ -6248,6 +6257,8 @@ export class SessionManager {
|
|
|
6248
6257
|
newHeader,
|
|
6249
6258
|
...materializedEntries.filter((entry): entry is SessionEntry => entry.type !== "session"),
|
|
6250
6259
|
];
|
|
6260
|
+
const forkSnapshotHook = SessionManagerTestHooks.afterForkSnapshot;
|
|
6261
|
+
if (forkSnapshotHook) await forkSnapshotHook();
|
|
6251
6262
|
|
|
6252
6263
|
await this.#closePersistWriter();
|
|
6253
6264
|
this.#persistChain = Promise.resolve();
|
|
@@ -9603,6 +9614,7 @@ export class SessionManager {
|
|
|
9603
9614
|
authority,
|
|
9604
9615
|
);
|
|
9605
9616
|
} catch (error) {
|
|
9617
|
+
if (error instanceof Error && error.message === "migration_busy") throw new SessionMigrationBusyError();
|
|
9606
9618
|
if (error instanceof Error && error.message.startsWith("Managed root authority changed"))
|
|
9607
9619
|
managedDestinationStore.assertBound();
|
|
9608
9620
|
throw error;
|
|
@@ -9611,6 +9623,7 @@ export class SessionManager {
|
|
|
9611
9623
|
managedDestinationStore.assertBound();
|
|
9612
9624
|
if (opened.code === "legacy_migration_disabled") throw new SessionMigrationPolicyError();
|
|
9613
9625
|
if (opened.code === "artifact_capacity_exceeded") throw new SessionArtifactCapacityError(opened.message);
|
|
9626
|
+
if (opened.code === "migration_busy") throw new SessionMigrationBusyError();
|
|
9614
9627
|
throw new Error(`Could not open managed session: ${opened.message}`);
|
|
9615
9628
|
}
|
|
9616
9629
|
assertManagedDestinationBound();
|
|
@@ -9706,7 +9719,15 @@ export class SessionManager {
|
|
|
9706
9719
|
throw new Error(`Could not open session: ${inspected.reason}`);
|
|
9707
9720
|
}
|
|
9708
9721
|
const opened = await SessionManager.openExistingStrict(inspected.identity, destination, storage, migrationPolicy);
|
|
9709
|
-
if (opened.kind === "error")
|
|
9722
|
+
if (opened.kind === "error") {
|
|
9723
|
+
if (opened.reason === "legacy_migration_disabled") throw new SessionMigrationPolicyError();
|
|
9724
|
+
if (opened.reason === "artifact_capacity_exceeded")
|
|
9725
|
+
throw new SessionArtifactCapacityError(
|
|
9726
|
+
opened.message ?? "Session artifacts exceed the migration capacity.",
|
|
9727
|
+
);
|
|
9728
|
+
if (opened.reason === "migration_busy") throw new SessionMigrationBusyError();
|
|
9729
|
+
throw new Error(`Could not open session: ${opened.reason}`);
|
|
9730
|
+
}
|
|
9710
9731
|
return opened.manager;
|
|
9711
9732
|
}
|
|
9712
9733
|
|
|
@@ -9844,6 +9865,7 @@ export class SessionManager {
|
|
|
9844
9865
|
const candidate = listing.owned.find(item => path.resolve(item.path) === path.resolve(sessionPath));
|
|
9845
9866
|
if (!candidate) throw new Error("Session is not an authorized managed candidate.");
|
|
9846
9867
|
const deleted = await deleteManagedSessionCandidate(resolved.scope, candidate);
|
|
9868
|
+
if (deleted.kind === "error" && deleted.code === "migration_busy") throw new SessionMigrationBusyError();
|
|
9847
9869
|
if (deleted.kind !== "deleted" && deleted.kind !== "already_deleted")
|
|
9848
9870
|
throw new Error(`Could not delete managed session: ${deleted.message}`);
|
|
9849
9871
|
}
|
|
@@ -10389,6 +10411,7 @@ export class SessionManager {
|
|
|
10389
10411
|
return { kind: "error", reason: "legacy_migration_disabled" };
|
|
10390
10412
|
if (error instanceof SessionArtifactCapacityError)
|
|
10391
10413
|
return { kind: "error", reason: "artifact_capacity_exceeded", message: error.message };
|
|
10414
|
+
if (error instanceof SessionMigrationBusyError) return { kind: "error", reason: "migration_busy" };
|
|
10392
10415
|
if (
|
|
10393
10416
|
error instanceof Error &&
|
|
10394
10417
|
(error.message.includes("source_changed") || error.message.includes("changed before migration"))
|
|
@@ -10451,6 +10474,7 @@ export class SessionManager {
|
|
|
10451
10474
|
throw new SessionArtifactCapacityError(
|
|
10452
10475
|
opened.message ?? "Session artifacts exceed the migration capacity.",
|
|
10453
10476
|
);
|
|
10477
|
+
if (opened.reason === "migration_busy") throw new SessionMigrationBusyError();
|
|
10454
10478
|
return undefined;
|
|
10455
10479
|
}
|
|
10456
10480
|
return opened.manager;
|
|
@@ -312,6 +312,7 @@ export type VerifiedSessionDeleteResult =
|
|
|
312
312
|
artifactsIdentity: SessionStorageFileIdentity | undefined;
|
|
313
313
|
/** Identity-bound quarantine path retained when recursive cleanup failed. */
|
|
314
314
|
detachedArtifactsPath: string;
|
|
315
|
+
artifactsPayloadDurable?: true;
|
|
315
316
|
/** Native snapshot required for an identity-bound recursive retry. */
|
|
316
317
|
artifactsTree: NativeDirectoryTreeSnapshot;
|
|
317
318
|
/** Transcript identity (unchanged) for retry binding. */
|
|
@@ -328,6 +329,7 @@ export type VerifiedSessionDeleteResult =
|
|
|
328
329
|
transcriptIdentity: SessionStorageFileIdentity;
|
|
329
330
|
/** Optional identity-bound transcript quarantine path for restart cleanup. */
|
|
330
331
|
detachedTranscriptPath?: string;
|
|
332
|
+
transcriptPayloadDurable?: true;
|
|
331
333
|
retainedSuccessorPath?: string;
|
|
332
334
|
retainedPlaceholderPath?: string;
|
|
333
335
|
retainedUnknownPath?: string;
|
|
@@ -423,6 +425,7 @@ type NativeDirectoryTreeApi = {
|
|
|
423
425
|
function nativeDirectoryTreeApi(): NativeDirectoryTreeApi {
|
|
424
426
|
return native as unknown as NativeDirectoryTreeApi;
|
|
425
427
|
}
|
|
428
|
+
|
|
426
429
|
function snapshotDirectoryTree(pathname: string): NativeDirectoryTreeSnapshot {
|
|
427
430
|
const result = nativeDirectoryTreeApi().snapshotDirectoryTree(pathname);
|
|
428
431
|
if (!result.ok || !result.snapshot)
|
|
@@ -438,10 +441,21 @@ function retainedTreeDoesNotExpandAuthority(
|
|
|
438
441
|
): boolean {
|
|
439
442
|
if (expected.rootDev !== retained.rootDev || expected.rootIno !== retained.rootIno) return false;
|
|
440
443
|
const expectedEntries = new Map(expected.entries.map(entry => [entry.relativePath, entry]));
|
|
444
|
+
if (retained.entries.length > expected.entries.length) return false;
|
|
441
445
|
return retained.entries.every(entry => {
|
|
442
446
|
if (entry.relativePath === "") return entry.kind === "directory";
|
|
443
447
|
const authorized = expectedEntries.get(entry.relativePath);
|
|
444
|
-
|
|
448
|
+
if (
|
|
449
|
+
authorized === undefined ||
|
|
450
|
+
authorized.kind !== entry.kind ||
|
|
451
|
+
authorized.dev !== entry.dev ||
|
|
452
|
+
authorized.ino !== entry.ino ||
|
|
453
|
+
authorized.nlink !== entry.nlink
|
|
454
|
+
)
|
|
455
|
+
return false;
|
|
456
|
+
if (entry.kind !== "file") return entry.size === authorized.size;
|
|
457
|
+
const scrubbed = entry.size === "0" && entry.sha256 === createHash("sha256").update("").digest("hex");
|
|
458
|
+
return scrubbed || (entry.size === authorized.size && entry.sha256 === authorized.sha256);
|
|
445
459
|
});
|
|
446
460
|
}
|
|
447
461
|
|
|
@@ -938,7 +952,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
938
952
|
"Transcript parent identity does not match authorization",
|
|
939
953
|
);
|
|
940
954
|
const authorizedTranscriptParentIdentity = transcriptParentIdentity ?? parentIdentity;
|
|
941
|
-
if (detachedArtifactsPath) {
|
|
955
|
+
if (detachedArtifactsPath && !artifactsRemoved) {
|
|
942
956
|
if (
|
|
943
957
|
!expectedArtifactsIdentity ||
|
|
944
958
|
path.dirname(detachedArtifactsPath) !== path.dirname(transcriptPath) ||
|
|
@@ -997,6 +1011,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
997
1011
|
artifactsIdentity: expectedArtifactsIdentity,
|
|
998
1012
|
detachedArtifactsPath: retainedRoot,
|
|
999
1013
|
artifactsTree: retainedTree,
|
|
1014
|
+
...((removal as typeof removal & { payloadDurable?: boolean }).payloadDurable === true
|
|
1015
|
+
? { artifactsPayloadDurable: true as const }
|
|
1016
|
+
: {}),
|
|
1000
1017
|
...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
|
|
1001
1018
|
? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
|
|
1002
1019
|
: {}),
|
|
@@ -1045,6 +1062,24 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1045
1062
|
"Artifact path reappeared after durable artifact-phase completion",
|
|
1046
1063
|
);
|
|
1047
1064
|
}
|
|
1065
|
+
if (artifactsRemoved && detachedArtifactsPath && expectedArtifactsIdentity && expectedArtifactsTree) {
|
|
1066
|
+
const retainedIdentity = this.#optionalDirectoryIdentity(detachedArtifactsPath);
|
|
1067
|
+
if (
|
|
1068
|
+
!retainedIdentity ||
|
|
1069
|
+
retainedIdentity.dev !== expectedArtifactsIdentity.dev ||
|
|
1070
|
+
retainedIdentity.ino !== expectedArtifactsIdentity.ino
|
|
1071
|
+
)
|
|
1072
|
+
throw new SessionDeleteVerificationError(
|
|
1073
|
+
"artifacts",
|
|
1074
|
+
"Retained artifact root identity changed before transcript cleanup",
|
|
1075
|
+
);
|
|
1076
|
+
const retainedTree = snapshotDirectoryTree(detachedArtifactsPath);
|
|
1077
|
+
if (!retainedTreeDoesNotExpandAuthority(expectedArtifactsTree, retainedTree))
|
|
1078
|
+
throw new SessionDeleteVerificationError(
|
|
1079
|
+
"artifacts",
|
|
1080
|
+
"Partial artifact cleanup expanded retained tree authority",
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1048
1083
|
if (!artifactsIdentity && expectedArtifactsIdentity && !detachedArtifactsPath && !artifactsRemoved) {
|
|
1049
1084
|
// Absence at the original path alone is not completion: native recursive removal
|
|
1050
1085
|
// may retain the planned root or its deterministic `.removing` final-stage root.
|
|
@@ -1116,12 +1151,22 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1116
1151
|
throw new Error("parent_changed");
|
|
1117
1152
|
fs.fsyncSync(descriptor);
|
|
1118
1153
|
} catch (error) {
|
|
1119
|
-
|
|
1154
|
+
return {
|
|
1155
|
+
kind: "cleanup_pending",
|
|
1156
|
+
phase: "artifacts",
|
|
1157
|
+
error: new SessionDeleteVerificationError("artifacts", "durability_failed", {
|
|
1158
|
+
cause: toError(error),
|
|
1159
|
+
}),
|
|
1160
|
+
artifactsIdentity,
|
|
1161
|
+
detachedArtifactsPath: detach.detachedPath,
|
|
1162
|
+
artifactsTree,
|
|
1163
|
+
transcriptIdentity,
|
|
1164
|
+
};
|
|
1120
1165
|
} finally {
|
|
1121
1166
|
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
1122
1167
|
}
|
|
1123
1168
|
}
|
|
1124
|
-
if (!detach.ok) {
|
|
1169
|
+
if (!detach.ok && process.platform === "win32") {
|
|
1125
1170
|
return {
|
|
1126
1171
|
kind: "cleanup_pending",
|
|
1127
1172
|
phase: "artifacts",
|
|
@@ -1132,7 +1177,6 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1132
1177
|
...(detach.retainedSuccessorPath ? { retainedSuccessorPath: detach.retainedSuccessorPath } : {}),
|
|
1133
1178
|
...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
|
|
1134
1179
|
...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
|
|
1135
|
-
|
|
1136
1180
|
transcriptIdentity,
|
|
1137
1181
|
};
|
|
1138
1182
|
}
|
|
@@ -1173,6 +1217,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1173
1217
|
artifactsIdentity,
|
|
1174
1218
|
detachedArtifactsPath: retainedRoot,
|
|
1175
1219
|
artifactsTree: retainedTree,
|
|
1220
|
+
...((removal as typeof removal & { payloadDurable?: boolean }).payloadDurable === true
|
|
1221
|
+
? { artifactsPayloadDurable: true as const }
|
|
1222
|
+
: {}),
|
|
1176
1223
|
...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
|
|
1177
1224
|
? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
|
|
1178
1225
|
: {}),
|
|
@@ -1223,6 +1270,13 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1223
1270
|
quarantineName: path.basename(plannedTranscriptPath),
|
|
1224
1271
|
});
|
|
1225
1272
|
if (!deletion.ok) {
|
|
1273
|
+
if (
|
|
1274
|
+
deletion.code === "cleanup_pending" &&
|
|
1275
|
+
(deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true &&
|
|
1276
|
+
deletion.retainedSuccessorPath === undefined &&
|
|
1277
|
+
deletion.retainedUnknownPath === undefined
|
|
1278
|
+
)
|
|
1279
|
+
return { kind: "deleted" };
|
|
1226
1280
|
const error = exactUnlinkFailure(deletion);
|
|
1227
1281
|
const retainedAuthority =
|
|
1228
1282
|
deletion.detachedPath ||
|
|
@@ -1234,6 +1288,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1234
1288
|
kind: "cleanup_pending",
|
|
1235
1289
|
phase: "transcript",
|
|
1236
1290
|
error,
|
|
1291
|
+
...((deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true
|
|
1292
|
+
? { transcriptPayloadDurable: true as const }
|
|
1293
|
+
: {}),
|
|
1237
1294
|
transcriptIdentity,
|
|
1238
1295
|
detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
|
|
1239
1296
|
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
@@ -1288,6 +1345,13 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1288
1345
|
quarantineName: path.basename(plannedTranscriptPath),
|
|
1289
1346
|
});
|
|
1290
1347
|
if (!deletion.ok) {
|
|
1348
|
+
if (
|
|
1349
|
+
deletion.code === "cleanup_pending" &&
|
|
1350
|
+
(deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true &&
|
|
1351
|
+
deletion.retainedSuccessorPath === undefined &&
|
|
1352
|
+
deletion.retainedUnknownPath === undefined
|
|
1353
|
+
)
|
|
1354
|
+
return { kind: "deleted" };
|
|
1291
1355
|
const error = exactUnlinkFailure(deletion);
|
|
1292
1356
|
const retainedAuthority =
|
|
1293
1357
|
deletion.detachedPath ||
|
|
@@ -1299,6 +1363,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1299
1363
|
kind: "cleanup_pending",
|
|
1300
1364
|
phase: "transcript",
|
|
1301
1365
|
error,
|
|
1366
|
+
...((deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true
|
|
1367
|
+
? { transcriptPayloadDurable: true as const }
|
|
1368
|
+
: {}),
|
|
1302
1369
|
transcriptIdentity,
|
|
1303
1370
|
detachedTranscriptPath: deletion.detachedPath,
|
|
1304
1371
|
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
canApplyComposerSubmission,
|
|
30
30
|
type InteractiveModeContext,
|
|
31
31
|
} from "../modes/types";
|
|
32
|
+
import { ChatDaemonController } from "../sdk/bus/chat-daemon-control";
|
|
33
|
+
import type { NotificationProvider } from "../sdk/bus/config";
|
|
32
34
|
import {
|
|
33
35
|
buildNotificationStatusReport,
|
|
34
36
|
checkNotificationHealth,
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
recoverNotifications,
|
|
40
42
|
sendNotificationTest,
|
|
41
43
|
} from "../sdk/bus/notification-service";
|
|
44
|
+
import { TelegramDaemonController } from "../sdk/bus/telegram-daemon-control";
|
|
42
45
|
import { computeCacheMissCostSummary, formatCacheMissSummaryLines } from "../session/cache-economics";
|
|
43
46
|
import { formatModelOnboardingGuidance } from "../setup/model-onboarding-guidance";
|
|
44
47
|
import {
|
|
@@ -498,6 +501,48 @@ function buildChangelogCommandOutput(showFull: boolean): string {
|
|
|
498
501
|
return `${title}\n\n${changelogMarkdown}${hint}`;
|
|
499
502
|
}
|
|
500
503
|
|
|
504
|
+
type NotifyServiceArgs = { provider?: NotificationProvider; probe: boolean; message?: string } | { error: string };
|
|
505
|
+
|
|
506
|
+
function isNotificationProvider(value: string): value is NotificationProvider {
|
|
507
|
+
return value === "telegram" || value === "discord" || value === "slack";
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function parseNotifyServiceArgs(input: string, allowMessage: boolean): NotifyServiceArgs {
|
|
511
|
+
const tokens = input.trim().split(/\s+/).filter(Boolean);
|
|
512
|
+
let provider: NotificationProvider | undefined;
|
|
513
|
+
let probe = false;
|
|
514
|
+
const message: string[] = [];
|
|
515
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
516
|
+
const token = tokens[index]!;
|
|
517
|
+
if (token === "--probe") {
|
|
518
|
+
if (allowMessage) return { error: "--probe is valid only for /notify health." };
|
|
519
|
+
probe = true;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (token === "--provider" || token.startsWith("--provider=")) {
|
|
523
|
+
const value = token === "--provider" ? tokens[++index] : token.slice("--provider=".length);
|
|
524
|
+
if (!value || !isNotificationProvider(value)) {
|
|
525
|
+
return { error: "--provider must be telegram, discord, or slack." };
|
|
526
|
+
}
|
|
527
|
+
if (provider && provider !== value) return { error: "Conflicting notification providers were supplied." };
|
|
528
|
+
provider = value;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (!provider && message.length === 0 && isNotificationProvider(token)) {
|
|
532
|
+
provider = token;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (token.startsWith("--")) return { error: `Unknown notification option: ${token}` };
|
|
536
|
+
message.push(token);
|
|
537
|
+
}
|
|
538
|
+
if (!allowMessage && message.length > 0) return { error: "Health accepts only a provider and --probe." };
|
|
539
|
+
return {
|
|
540
|
+
...(provider ? { provider } : {}),
|
|
541
|
+
probe,
|
|
542
|
+
...(message.length > 0 ? { message: message.join(" ") } : {}),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
501
546
|
const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime): SlashCommandResult => {
|
|
502
547
|
runtime.ctx.editor.setText("");
|
|
503
548
|
void runtime.ctx.shutdown();
|
|
@@ -514,8 +559,12 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
514
559
|
{ name: "on", description: "Enable notifications for this session" },
|
|
515
560
|
{ name: "off", description: "Disable notifications for this session" },
|
|
516
561
|
{ name: "status", description: "Show notification configuration (no secrets)" },
|
|
517
|
-
{
|
|
518
|
-
|
|
562
|
+
{
|
|
563
|
+
name: "health",
|
|
564
|
+
description: "Config, ownership, endpoint, and selected-provider health",
|
|
565
|
+
usage: "[provider] [--probe]",
|
|
566
|
+
},
|
|
567
|
+
{ name: "test", description: "Send a test notification", usage: "[provider|--provider provider] [message]" },
|
|
519
568
|
{ name: "recovery", description: "Clear dead-owner locks and stale endpoint files" },
|
|
520
569
|
{ name: "setup", description: "How to pair a Telegram bot (run in a terminal)" },
|
|
521
570
|
],
|
|
@@ -541,12 +590,41 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
541
590
|
await runtime.output(formatNotificationStatusReport(buildNotificationStatusReport(runtime.settings)));
|
|
542
591
|
return commandConsumed();
|
|
543
592
|
case "health": {
|
|
544
|
-
const
|
|
593
|
+
const parsed = parseNotifyServiceArgs(rest, false);
|
|
594
|
+
if ("error" in parsed) {
|
|
595
|
+
return usage(`Usage: /notify health [telegram|discord|slack] [--probe]\n${parsed.error}`, runtime);
|
|
596
|
+
}
|
|
597
|
+
const report = await checkNotificationHealth({
|
|
598
|
+
settings: runtime.settings,
|
|
599
|
+
stateRoot,
|
|
600
|
+
provider: parsed.provider,
|
|
601
|
+
probe: parsed.probe,
|
|
602
|
+
});
|
|
545
603
|
await runtime.output(formatNotificationHealthReport(report));
|
|
546
604
|
return commandConsumed();
|
|
547
605
|
}
|
|
548
606
|
case "test": {
|
|
549
|
-
const
|
|
607
|
+
const parsed = parseNotifyServiceArgs(rest, true);
|
|
608
|
+
if ("error" in parsed) {
|
|
609
|
+
return usage(
|
|
610
|
+
`Usage: /notify test [telegram|discord|slack|--provider provider] [message]\n${parsed.error}`,
|
|
611
|
+
runtime,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
const result = await sendNotificationTest({
|
|
615
|
+
settings: runtime.settings,
|
|
616
|
+
provider: parsed.provider,
|
|
617
|
+
text: parsed.message,
|
|
618
|
+
deps: {
|
|
619
|
+
providerRuntimeStatus: async provider => {
|
|
620
|
+
const status =
|
|
621
|
+
provider === "telegram"
|
|
622
|
+
? await new TelegramDaemonController(runtime.settings).status()
|
|
623
|
+
: await new ChatDaemonController(runtime.settings, provider).status();
|
|
624
|
+
return status.health === "running" ? "ready" : "inactive";
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
});
|
|
550
628
|
await runtime.output(formatNotificationTestResult(result));
|
|
551
629
|
return commandConsumed();
|
|
552
630
|
}
|
package/src/task/index.ts
CHANGED
|
@@ -935,8 +935,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
935
935
|
): { ok: boolean; disposition?: DuplicateDisposition; error?: string; identity: DuplicateIdentity } => {
|
|
936
936
|
const identity = duplicateIdentityForTask(task, params.agent, ownerId, parentSession);
|
|
937
937
|
const key = duplicateIdentityKey(identity);
|
|
938
|
-
const predecessors = manager
|
|
939
|
-
.getSubagentRecords({ ownerId })
|
|
938
|
+
const predecessors = (manager.getSubagentRecords?.({ ownerId }) ?? [])
|
|
940
939
|
.filter(record => {
|
|
941
940
|
if (record.subagentId === selfSubagentId || record.subagentId === subagentId) return false;
|
|
942
941
|
if (record.status !== "running" && record.status !== "paused" && record.status !== "queued")
|
|
@@ -1139,7 +1138,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1139
1138
|
}
|
|
1140
1139
|
break;
|
|
1141
1140
|
}
|
|
1142
|
-
if (frozenForkSeed)
|
|
1141
|
+
if (frozenForkSeed) {
|
|
1142
|
+
frozenForkSeeds.set(uniqueId, frozenForkSeed);
|
|
1143
|
+
// The async callback may execute through a compatibility path that retains
|
|
1144
|
+
// the caller's task id instead of the allocated artifact id. Preserve the
|
|
1145
|
+
// same immutable seed under both identities so detached execution never
|
|
1146
|
+
// rebuilds context after dispatch.
|
|
1147
|
+
frozenForkSeeds.set(taskItem.id, frozenForkSeed);
|
|
1148
|
+
}
|
|
1143
1149
|
const singleParams: TaskParams = { ...params, tasks: [taskItem] };
|
|
1144
1150
|
const label = uniqueId;
|
|
1145
1151
|
try {
|
package/src/tools/ask.ts
CHANGED
|
@@ -1613,7 +1613,7 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
|
|
|
1613
1613
|
signal,
|
|
1614
1614
|
initialSelection,
|
|
1615
1615
|
navigation: options?.navigation,
|
|
1616
|
-
scrollTitleRows:
|
|
1616
|
+
scrollTitleRows: DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS,
|
|
1617
1617
|
otherOptionLabel,
|
|
1618
1618
|
autoSelectOnTimeout: !intentContract(q.deepInterview) && !intentReview(q.deepInterview),
|
|
1619
1619
|
clarificationOptionLabel,
|
package/src/tools/bash.ts
CHANGED
|
@@ -201,7 +201,7 @@ function formatBashFailureMessage(
|
|
|
201
201
|
const bodyText = removeTrailingFailureCause(text, statusCause);
|
|
202
202
|
const suffixParts: string[] = [];
|
|
203
203
|
const reference = artifactReferenceForResult(result);
|
|
204
|
-
if (reference) suffixParts.push(reference);
|
|
204
|
+
if (reference && !artifactReferenceIsReachable(text, result)) suffixParts.push(reference);
|
|
205
205
|
const writerNotice = artifactWriterFailureNotice(result);
|
|
206
206
|
if (writerNotice) suffixParts.push(writerNotice);
|
|
207
207
|
if (statusCause) suffixParts.push(statusCause);
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
5
|
import { getTelegramFileSink } from "../sdk/bus/attachment-registry";
|
|
6
|
-
import { getNotificationConfig,
|
|
6
|
+
import { getNotificationConfig, isProviderEffectivelyEnabled } from "../sdk/bus/config";
|
|
7
7
|
import type { ToolSession } from "./index";
|
|
8
8
|
|
|
9
9
|
const TELEGRAM_SEND_MAX_FILE_BYTES = 50 * 1024 * 1024;
|
|
@@ -41,7 +41,9 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
|
|
|
41
41
|
constructor(private readonly session: ToolSession) {}
|
|
42
42
|
|
|
43
43
|
static createIf(session: ToolSession): TelegramSendTool | null {
|
|
44
|
-
return
|
|
44
|
+
return isProviderEffectivelyEnabled(getNotificationConfig(session.settings), "telegram")
|
|
45
|
+
? new TelegramSendTool(session)
|
|
46
|
+
: null;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|