@gajae-code/coding-agent 0.11.5 → 0.11.7
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 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +67 -7
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +5 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +19 -5
- package/dist/types/main.d.ts +24 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-status-header.d.ts +1 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +13 -3
- package/dist/types/sdk/bus/telegram-daemon.d.ts +61 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +28 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +21 -7
- package/dist/types/session/internal/managed-session-storage.d.ts +10 -8
- package/dist/types/session/session-manager.d.ts +35 -4
- package/dist/types/session/yield-queue.d.ts +8 -0
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/tools/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +16 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +34 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +42 -9
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profiles.ts +18 -0
- package/src/config/settings-schema.ts +71 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -1
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/gjc-runtime/ultragoal-guard.ts +88 -10
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +275 -12
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +9 -8
- package/src/internal-urls/local-protocol.ts +32 -6
- package/src/main.ts +87 -28
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +31 -3
- package/src/modes/components/tool-status-header.ts +6 -1
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +15 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -2
- package/src/modes/controllers/input-controller.ts +31 -0
- package/src/modes/controllers/selector-controller.ts +106 -12
- package/src/modes/interactive-mode.ts +16 -7
- package/src/modes/runtime-init.ts +16 -2
- package/src/modes/types.ts +1 -0
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- package/src/runtime-mcp/transports/http.ts +50 -16
- package/src/sdk/bus/chat-daemon-control.ts +20 -7
- package/src/sdk/bus/index.ts +8 -7
- package/src/sdk/bus/lifecycle-commands.ts +16 -7
- package/src/sdk/bus/notification-service.ts +41 -18
- package/src/sdk/bus/telegram-daemon-contract.ts +13 -3
- package/src/sdk/bus/telegram-daemon-control.ts +26 -8
- package/src/sdk/bus/telegram-daemon.ts +1736 -338
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +14 -5
- package/src/sdk/protocol/operation-inventory.generated.json +29 -0
- package/src/sdk/session.ts +29 -5
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +1212 -768
- package/src/session/blob-store.ts +19 -6
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +313 -57
- package/src/session/internal/managed-session-storage.ts +146 -35
- package/src/session/session-manager.ts +385 -85
- package/src/session/yield-queue.ts +20 -0
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +37 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/tools/ask-answer-registry.ts +15 -1
- package/src/tools/ask.ts +1 -1
- package/src/tools/fetch.ts +13 -131
- package/src/tools/image-gen.ts +301 -20
- package/src/tools/read.ts +2 -0
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +138 -27
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
|
@@ -251,19 +251,29 @@ export function formatNotificationStatusReport(report: NotificationStatusReport)
|
|
|
251
251
|
|
|
252
252
|
// --- endpoint / daemon file readers -------------------------------------
|
|
253
253
|
|
|
254
|
-
interface
|
|
254
|
+
export interface NotificationEndpointView {
|
|
255
255
|
sessionId: string;
|
|
256
256
|
pid: number | undefined;
|
|
257
257
|
stale: boolean;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
type
|
|
261
|
-
|
|
260
|
+
export type NotificationEndpointLiveness = "live" | "dead" | "unknown";
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Classification used by recovery and startup takeover. A file is an endpoint
|
|
264
|
+
* only when it has endpoint authority fields; lifecycle/audit records are never
|
|
265
|
+
* candidates for endpoint cleanup.
|
|
266
|
+
*/
|
|
267
|
+
export type NotificationEndpointClassification =
|
|
268
|
+
| {
|
|
269
|
+
kind: "endpoint";
|
|
270
|
+
view: NotificationEndpointView;
|
|
271
|
+
liveness: NotificationEndpointLiveness;
|
|
272
|
+
identity: NotificationEndpointFileIdentity;
|
|
273
|
+
}
|
|
262
274
|
| { kind: "non-endpoint" }
|
|
263
275
|
| { kind: "unreadable" };
|
|
264
276
|
|
|
265
|
-
type EndpointLiveness = "live" | "dead" | "unknown";
|
|
266
|
-
|
|
267
277
|
/**
|
|
268
278
|
* Classify an endpoint using owner-proof semantics. An endpoint is only `dead`
|
|
269
279
|
* with positive proof: an explicit `stale` tombstone, or a recorded pid that is
|
|
@@ -271,7 +281,10 @@ type EndpointLiveness = "live" | "dead" | "unknown";
|
|
|
271
281
|
* must never be treated as dead — removing it could delete a live session's
|
|
272
282
|
* discovery file that simply omitted a pid.
|
|
273
283
|
*/
|
|
274
|
-
function
|
|
284
|
+
export function notificationEndpointLiveness(
|
|
285
|
+
view: NotificationEndpointView,
|
|
286
|
+
pidAlive: (pid: number) => boolean,
|
|
287
|
+
): NotificationEndpointLiveness {
|
|
275
288
|
if (view.stale) return "dead";
|
|
276
289
|
if (view.pid === undefined) return "unknown";
|
|
277
290
|
return pidAlive(view.pid) ? "live" : "dead";
|
|
@@ -314,11 +327,19 @@ function isCanonicalLifecycleArtifactName(name: string): boolean {
|
|
|
314
327
|
);
|
|
315
328
|
}
|
|
316
329
|
|
|
317
|
-
function unreadableEndpointResult(file: string):
|
|
330
|
+
function unreadableEndpointResult(file: string): NotificationEndpointClassification {
|
|
318
331
|
return isCanonicalLifecycleArtifactName(path.basename(file)) ? { kind: "non-endpoint" } : { kind: "unreadable" };
|
|
319
332
|
}
|
|
320
333
|
|
|
321
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Read and classify one endpoint candidate. The returned identity belongs to
|
|
336
|
+
* exactly the bytes inspected and is required for any later deletion.
|
|
337
|
+
*/
|
|
338
|
+
export async function classifyNotificationEndpoint(
|
|
339
|
+
fs: Pick<NotificationServiceFs, "readEndpointFile">,
|
|
340
|
+
file: string,
|
|
341
|
+
pidAlive: (pid: number) => boolean,
|
|
342
|
+
): Promise<NotificationEndpointClassification> {
|
|
322
343
|
let endpoint: NotificationEndpointFile;
|
|
323
344
|
let raw: string;
|
|
324
345
|
try {
|
|
@@ -334,16 +355,19 @@ async function readEndpointView(fs: NotificationServiceFs, file: string): Promis
|
|
|
334
355
|
return unreadableEndpointResult(file);
|
|
335
356
|
}
|
|
336
357
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { kind: "non-endpoint" };
|
|
358
|
+
if (path.basename(file) === "broker.json") return { kind: "non-endpoint" };
|
|
337
359
|
const rec = parsed as Record<string, unknown>;
|
|
338
360
|
if (isLifecycleArtifact(rec) || typeof rec.url !== "string" || typeof rec.token !== "string")
|
|
339
361
|
return { kind: "non-endpoint" };
|
|
362
|
+
const view: NotificationEndpointView = {
|
|
363
|
+
sessionId: typeof rec.sessionId === "string" ? rec.sessionId : path.basename(file, ".json"),
|
|
364
|
+
pid: safePositiveInteger(rec.pid),
|
|
365
|
+
stale: rec.stale === true,
|
|
366
|
+
};
|
|
340
367
|
return {
|
|
341
368
|
kind: "endpoint",
|
|
342
|
-
view
|
|
343
|
-
|
|
344
|
-
pid: safePositiveInteger(rec.pid),
|
|
345
|
-
stale: rec.stale === true,
|
|
346
|
-
},
|
|
369
|
+
view,
|
|
370
|
+
liveness: notificationEndpointLiveness(view, pidAlive),
|
|
347
371
|
identity: endpoint.identity,
|
|
348
372
|
};
|
|
349
373
|
}
|
|
@@ -604,14 +628,13 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
604
628
|
let unknownEndpoints = 0;
|
|
605
629
|
let unreadable = 0;
|
|
606
630
|
for (const name of files) {
|
|
607
|
-
const record = await
|
|
631
|
+
const record = await classifyNotificationEndpoint(fs, path.join(dir, name), pidAlive);
|
|
608
632
|
if (record.kind === "non-endpoint") continue;
|
|
609
633
|
if (record.kind === "unreadable") {
|
|
610
634
|
unreadable += 1;
|
|
611
635
|
continue;
|
|
612
636
|
}
|
|
613
|
-
|
|
614
|
-
switch (endpointLiveness(view, pidAlive)) {
|
|
637
|
+
switch (record.liveness) {
|
|
615
638
|
case "live":
|
|
616
639
|
live += 1;
|
|
617
640
|
break;
|
|
@@ -1040,7 +1063,7 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
|
|
|
1040
1063
|
let unreadable = 0;
|
|
1041
1064
|
for (const name of files) {
|
|
1042
1065
|
const file = path.join(dir, name);
|
|
1043
|
-
const record = await
|
|
1066
|
+
const record = await classifyNotificationEndpoint(fs, file, pidAlive);
|
|
1044
1067
|
if (record.kind === "non-endpoint") continue;
|
|
1045
1068
|
if (record.kind === "unreadable") {
|
|
1046
1069
|
// Leave unparseable files untouched: they may be mid-write by a live server.
|
|
@@ -1048,7 +1071,7 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
|
|
|
1048
1071
|
continue;
|
|
1049
1072
|
}
|
|
1050
1073
|
const view = record.view;
|
|
1051
|
-
if (
|
|
1074
|
+
if (record.liveness !== "dead") {
|
|
1052
1075
|
// Keep live AND unknown (PID-less) endpoints: only positive proof of
|
|
1053
1076
|
// death (a stale tombstone or a dead pid) authorizes removal.
|
|
1054
1077
|
kept += 1;
|
|
@@ -16,7 +16,17 @@ export const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
16
16
|
* The current development baseline already includes #2299's generation 4,
|
|
17
17
|
* incarnation fencing in generation 5, owner-lock authority in generation 6,
|
|
18
18
|
* identity-atomic transition markers in generation 7, stable signaling plus
|
|
19
|
-
* tri-state foreign-owner provenance in generation 8,
|
|
20
|
-
* filesystem authority changes in generation 9
|
|
19
|
+
* tri-state foreign-owner provenance in generation 8, retained managed
|
|
20
|
+
* filesystem authority changes in generation 9, SDK-startup auto-reclaim of a
|
|
21
|
+
* confirmed-dead owner's lock in generation 10, legacy stopped-tombstone
|
|
22
|
+
* reclamation in generation 11, force-escalated SIGKILL of an unresponsive
|
|
23
|
+
* older-generation owner during automatic generation-upgrade reload in
|
|
24
|
+
* generation 12, restored macOS daemon signaling (kill(2) with a start-time
|
|
25
|
+
* incarnation recheck, replacing the darwin no-op) in generation 13, retained
|
|
26
|
+
* legacy stopped-lock reclamation in generation 14, Windows expected-identity
|
|
27
|
+
* ACL verification and repair in generation 15, identity-fenced stale endpoint
|
|
28
|
+
* startup recovery in generation 16, Telegram topic recovery authority fencing
|
|
29
|
+
* in generation 17, and fail-closed blank-token validation plus lifecycle-startup
|
|
30
|
+
* stop fencing in generation 18.
|
|
21
31
|
*/
|
|
22
|
-
export const DAEMON_GENERATION =
|
|
32
|
+
export const DAEMON_GENERATION = 18;
|
|
@@ -113,18 +113,29 @@ export interface DaemonProcessReference {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
function defaultProcessReference(pid: number, platform = os.platform()): DaemonProcessReference | undefined {
|
|
116
|
-
// Process.fromPid on Darwin still resolves identity and then signals a numeric
|
|
117
|
-
// PID. A process can exit and its PID be reused in that interval, so daemon
|
|
118
|
-
// control must not use it as privileged signaling authority.
|
|
119
|
-
if (platform === "darwin") return undefined;
|
|
120
116
|
try {
|
|
121
117
|
const processRef = Process.fromPid(pid);
|
|
122
118
|
if (!processRef || !isProcessIncarnation(processRef.incarnation)) return undefined;
|
|
119
|
+
const incarnation = processRef.incarnation;
|
|
123
120
|
return {
|
|
124
|
-
incarnation
|
|
121
|
+
incarnation,
|
|
125
122
|
signalRoot: signal => {
|
|
126
123
|
const nativeSignal = os.constants.signals[signal];
|
|
127
124
|
if (nativeSignal === undefined) throw new Error(`Unsupported signal: ${signal}`);
|
|
125
|
+
// macOS exposes no pidfd and the native signal_root is a no-op there, so
|
|
126
|
+
// the daemon control plane previously had NO way to signal a live owner —
|
|
127
|
+
// every stop/reload of a live/hung daemon refused ("ownership changed;
|
|
128
|
+
// refusing to signal"), and only an external `kill -9` could recover it.
|
|
129
|
+
// Signal by numeric PID via kill(2), but re-read the immutable start-time
|
|
130
|
+
// incarnation immediately beforehand so a PID that exited and was reused
|
|
131
|
+
// since capture is never signaled; the residual window is the few
|
|
132
|
+
// instructions between this recheck and kill(2).
|
|
133
|
+
if (platform === "darwin") {
|
|
134
|
+
const current = Process.fromPid(pid) as { incarnation?: unknown } | null;
|
|
135
|
+
if (!current || current.incarnation !== incarnation) throw new Error("Pinned process is already gone");
|
|
136
|
+
process.kill(pid, signal);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
128
139
|
const rootProcess = processRef as typeof processRef & { signalRoot(signal: number): boolean };
|
|
129
140
|
if (!rootProcess.signalRoot(nativeSignal)) throw new Error("Pinned process is already gone");
|
|
130
141
|
},
|
|
@@ -167,8 +178,8 @@ function defaultPidAlive(pid: number): boolean {
|
|
|
167
178
|
try {
|
|
168
179
|
process.kill(pid, 0);
|
|
169
180
|
return true;
|
|
170
|
-
} catch {
|
|
171
|
-
return
|
|
181
|
+
} catch (error) {
|
|
182
|
+
return (error as NodeJS.ErrnoException).code !== "ESRCH";
|
|
172
183
|
}
|
|
173
184
|
}
|
|
174
185
|
|
|
@@ -366,7 +377,14 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
366
377
|
}
|
|
367
378
|
|
|
368
379
|
async reloadForGenerationUpgrade(opts: DaemonOperationOptions = {}): Promise<TelegramGenerationReloadResult> {
|
|
369
|
-
|
|
380
|
+
// A generation upgrade MUST replace an incompatible older-generation owner to
|
|
381
|
+
// avoid a permanent single-poller deadlock. Unlike a manual `gjc daemon
|
|
382
|
+
// reload`, this automatic path force-escalates to SIGKILL when the old owner
|
|
383
|
+
// ignores the cooperative SIGTERM within the graceful timeout, so SDK startup
|
|
384
|
+
// self-recovers instead of failing closed and asking the operator to rerun
|
|
385
|
+
// with --force. The SIGKILL remains fenced to the still-live, still-matching
|
|
386
|
+
// captured owner (same ownerId + pid), so a fresh replacement is never killed.
|
|
387
|
+
const operation = await this.stopOrReload("reload", { ...opts, force: true });
|
|
370
388
|
if (!operation.ok) return { outcome: "failed", operation };
|
|
371
389
|
const after = await this.status();
|
|
372
390
|
return after.health === "running" ? { outcome: "ready", operation } : { outcome: "failed", operation };
|