@gajae-code/coding-agent 0.16.4 → 0.16.6
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 +54 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/helpers.d.ts +33 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +4 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +12 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +4 -2
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +4 -3
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +9 -7
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +198 -14
- package/src/config/model-preset-registry.ts +115 -16
- package/src/config/model-registry.ts +16 -12
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +50 -62
- package/src/discovery/helpers.ts +297 -32
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +20 -2
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +80 -47
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +29 -9
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/session-state-lock.ts +51 -8
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +6 -1
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/tool-status-header.ts +3 -1
- package/src/modes/controllers/command-controller.ts +1 -0
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/interactive-mode.ts +6 -5
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +16 -3
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/manager.ts +20 -2
- package/src/runtime-mcp/pool.test.ts +20 -11
- package/src/sdk/broker/broker.ts +119 -28
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +35 -5
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +387 -48
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +2 -2
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +50 -8
- package/src/sdk/host/session-runtime.ts +158 -51
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session.ts +155 -26
- package/src/sdk/transport/relay.ts +35 -13
- package/src/session/agent-session.ts +117 -52
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +49 -43
- package/src/setup/paseo/setup-deps.ts +1 -1
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/system-prompt.ts +25 -3
- package/src/task/executor.ts +5 -0
- package/src/task/index.ts +4 -0
- package/src/tools/bash.ts +28 -11
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +2 -0
- package/src/tools/job.ts +13 -2
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +15 -0
- package/src/tools/subagent.ts +3 -2
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/tools/ultragoal-ask-guard.ts +2 -2
|
@@ -152,6 +152,26 @@ function evictExpired(db: Database, hardTtlMs: number): void {
|
|
|
152
152
|
*/
|
|
153
153
|
const SWEEP_INTERVAL_MS = 60_000;
|
|
154
154
|
let lastSweepAt = 0;
|
|
155
|
+
const backgroundRefreshes = new Map<string, { generation: number; controller: AbortController }>();
|
|
156
|
+
let backgroundRefreshGeneration = 0;
|
|
157
|
+
const BACKGROUND_REFRESH_TIMEOUT_MS = 30_000;
|
|
158
|
+
|
|
159
|
+
function cacheIdentityKey(
|
|
160
|
+
authKey: string,
|
|
161
|
+
repo: string,
|
|
162
|
+
kind: CacheKind,
|
|
163
|
+
number: number,
|
|
164
|
+
includeComments: boolean,
|
|
165
|
+
): string {
|
|
166
|
+
return JSON.stringify([authKey, normalizeRepo(repo), kind, number, includeComments]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function retireBackgroundRefresh(key: string): void {
|
|
170
|
+
const entry = backgroundRefreshes.get(key);
|
|
171
|
+
if (!entry) return;
|
|
172
|
+
entry.controller.abort();
|
|
173
|
+
backgroundRefreshes.delete(key);
|
|
174
|
+
}
|
|
155
175
|
|
|
156
176
|
function sweepIfDue(hardTtlMs: number): void {
|
|
157
177
|
const now = Date.now();
|
|
@@ -296,6 +316,12 @@ export function invalidate(
|
|
|
296
316
|
includeComments?: boolean,
|
|
297
317
|
authKey: string = DEFAULT_CACHE_AUTH_KEY,
|
|
298
318
|
): void {
|
|
319
|
+
if (includeComments === undefined) {
|
|
320
|
+
retireBackgroundRefresh(cacheIdentityKey(authKey, repo, kind, number, false));
|
|
321
|
+
retireBackgroundRefresh(cacheIdentityKey(authKey, repo, kind, number, true));
|
|
322
|
+
} else {
|
|
323
|
+
retireBackgroundRefresh(cacheIdentityKey(authKey, repo, kind, number, includeComments));
|
|
324
|
+
}
|
|
299
325
|
const db = openDb();
|
|
300
326
|
if (!db) return;
|
|
301
327
|
try {
|
|
@@ -342,6 +368,9 @@ export function resetForTests(): void {
|
|
|
342
368
|
cachedDb = null;
|
|
343
369
|
openAttempted = false;
|
|
344
370
|
lastSweepAt = 0;
|
|
371
|
+
for (const { controller } of backgroundRefreshes.values()) controller.abort();
|
|
372
|
+
backgroundRefreshes.clear();
|
|
373
|
+
backgroundRefreshGeneration += 1;
|
|
345
374
|
}
|
|
346
375
|
|
|
347
376
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -365,9 +394,10 @@ export interface CacheLookupOptions<T> {
|
|
|
365
394
|
* must bypass persistent cache reads/writes.
|
|
366
395
|
*/
|
|
367
396
|
authKey?: string | null;
|
|
368
|
-
fetchFresh: () => Promise<FreshResult<T>>;
|
|
397
|
+
fetchFresh: (signal?: AbortSignal) => Promise<FreshResult<T>>;
|
|
369
398
|
settings?: Settings | undefined;
|
|
370
399
|
now?: number;
|
|
400
|
+
signal?: AbortSignal;
|
|
371
401
|
}
|
|
372
402
|
|
|
373
403
|
export type CacheStatus = "miss" | "fresh" | "stale" | "disabled";
|
|
@@ -448,22 +478,41 @@ function scheduleBackgroundRefresh<T>(
|
|
|
448
478
|
kind: CacheKind,
|
|
449
479
|
number: number,
|
|
450
480
|
includeComments: boolean,
|
|
451
|
-
fetchFresh: () => Promise<FreshResult<T>>,
|
|
481
|
+
fetchFresh: (signal?: AbortSignal) => Promise<FreshResult<T>>,
|
|
452
482
|
): void {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
483
|
+
const key = cacheIdentityKey(authKey, repo, kind, number, includeComments);
|
|
484
|
+
if (backgroundRefreshes.has(key)) return;
|
|
485
|
+
const generation = backgroundRefreshGeneration;
|
|
486
|
+
const controller = new AbortController();
|
|
487
|
+
const entry = { generation, controller };
|
|
488
|
+
backgroundRefreshes.set(key, entry);
|
|
489
|
+
queueMicrotask(async () => {
|
|
490
|
+
const timeoutHandle = setTimeout(() => {
|
|
491
|
+
controller.abort(new Error("GitHub background refresh timed out"));
|
|
492
|
+
if (backgroundRefreshes.get(key) === entry) backgroundRefreshes.delete(key);
|
|
493
|
+
logger.debug("github cache: background refresh timed out", { repo, kind, number });
|
|
494
|
+
}, BACKGROUND_REFRESH_TIMEOUT_MS);
|
|
495
|
+
try {
|
|
496
|
+
const fresh = await fetchFresh(controller.signal);
|
|
497
|
+
if (
|
|
498
|
+
!controller.signal.aborted &&
|
|
499
|
+
backgroundRefreshes.get(key) === entry &&
|
|
500
|
+
backgroundRefreshGeneration === generation
|
|
501
|
+
) {
|
|
457
502
|
storeResult(authKey, repo, kind, number, includeComments, fresh, Date.now());
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
503
|
+
}
|
|
504
|
+
} catch (err) {
|
|
505
|
+
controller.abort();
|
|
506
|
+
logger.debug("github cache: background refresh failed", {
|
|
507
|
+
err: String(err),
|
|
508
|
+
repo,
|
|
509
|
+
kind,
|
|
510
|
+
number,
|
|
466
511
|
});
|
|
512
|
+
} finally {
|
|
513
|
+
clearTimeout(timeoutHandle);
|
|
514
|
+
if (backgroundRefreshes.get(key) === entry) backgroundRefreshes.delete(key);
|
|
515
|
+
}
|
|
467
516
|
});
|
|
468
517
|
}
|
|
469
518
|
|
|
@@ -473,9 +522,10 @@ export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise
|
|
|
473
522
|
const authKey = options.authKey === undefined ? DEFAULT_CACHE_AUTH_KEY : options.authKey;
|
|
474
523
|
|
|
475
524
|
if (!ttl.enabled || authKey === null) {
|
|
476
|
-
const fresh = await options.fetchFresh();
|
|
525
|
+
const fresh = await options.fetchFresh(options.signal);
|
|
477
526
|
return { ...fresh, status: "disabled", fetchedAt: now };
|
|
478
527
|
}
|
|
528
|
+
const cacheKey = cacheIdentityKey(authKey, options.repo, options.kind, options.number, options.includeComments);
|
|
479
529
|
|
|
480
530
|
// Enforce the *configured* hard TTL against on-disk rows. This is what
|
|
481
531
|
// makes `github.cache.hardTtlSec` a real retention cap rather than a soft
|
|
@@ -496,6 +546,7 @@ export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise
|
|
|
496
546
|
// Past hard TTL: drop the row eagerly so the on-disk exposure window
|
|
497
547
|
// is bounded even if `fetchFresh()` then fails (network down, gh
|
|
498
548
|
// auth lapse, etc.) and we never get to overwrite it.
|
|
549
|
+
retireBackgroundRefresh(cacheKey);
|
|
499
550
|
invalidate(options.repo, options.kind, options.number, options.includeComments, authKey);
|
|
500
551
|
} else if (age <= ttl.softMs) {
|
|
501
552
|
return {
|
|
@@ -524,8 +575,9 @@ export async function getOrFetchView<T>(options: CacheLookupOptions<T>): Promise
|
|
|
524
575
|
}
|
|
525
576
|
}
|
|
526
577
|
|
|
527
|
-
const fresh = await options.fetchFresh();
|
|
578
|
+
const fresh = await options.fetchFresh(options.signal);
|
|
528
579
|
const fetchedAt = Date.now();
|
|
580
|
+
retireBackgroundRefresh(cacheKey);
|
|
529
581
|
storeResult(authKey, options.repo, options.kind, options.number, options.includeComments, fresh, fetchedAt);
|
|
530
582
|
return { ...fresh, status: "miss", fetchedAt };
|
|
531
583
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -203,6 +203,8 @@ export interface ToolSession {
|
|
|
203
203
|
* Runtime discovery only; never threaded into capability loading.
|
|
204
204
|
*/
|
|
205
205
|
home?: string;
|
|
206
|
+
/** Resolver-owned classification for the session's selected agent directory. */
|
|
207
|
+
profileAuthority?: "default" | "custom";
|
|
206
208
|
skills?: Skill[];
|
|
207
209
|
/** Currently executing skill prompt, when this tool session is inside one. */
|
|
208
210
|
getActiveSkillState?: () => Pick<SkillActiveEntry, "skill" | "session_id"> | undefined;
|
package/src/tools/job.ts
CHANGED
|
@@ -256,10 +256,21 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
256
256
|
},
|
|
257
257
|
});
|
|
258
258
|
};
|
|
259
|
-
const
|
|
260
|
-
|
|
259
|
+
const { promise: progressError, reject: rejectProgress } = Promise.withResolvers<never>();
|
|
260
|
+
racePromises.push(progressError);
|
|
261
|
+
let progressTimer: Timer | undefined;
|
|
261
262
|
|
|
262
263
|
try {
|
|
264
|
+
progressTimer = onUpdate
|
|
265
|
+
? setInterval(() => {
|
|
266
|
+
try {
|
|
267
|
+
emitProgress();
|
|
268
|
+
} catch (error) {
|
|
269
|
+
rejectProgress(error);
|
|
270
|
+
}
|
|
271
|
+
}, PROGRESS_INTERVAL_MS)
|
|
272
|
+
: undefined;
|
|
273
|
+
emitProgress();
|
|
263
274
|
if (signal) {
|
|
264
275
|
const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<void>();
|
|
265
276
|
const onAbort = () => abortResolve();
|
package/src/tools/read.ts
CHANGED
|
@@ -818,10 +818,22 @@ export async function streamLinesFromFile(
|
|
|
818
818
|
fitBytes += separatorBytes + currentLineLength;
|
|
819
819
|
} else {
|
|
820
820
|
transitionToSplit();
|
|
821
|
-
pushSplit(
|
|
821
|
+
pushSplit(
|
|
822
|
+
lineIndex,
|
|
823
|
+
lineText,
|
|
824
|
+
currentLineLength,
|
|
825
|
+
currentLineDropped,
|
|
826
|
+
currentLineDropped || currentLineLength > ringBudgetBytes ? capturePartialPreview() : undefined,
|
|
827
|
+
);
|
|
822
828
|
}
|
|
823
829
|
} else {
|
|
824
|
-
pushSplit(
|
|
830
|
+
pushSplit(
|
|
831
|
+
lineIndex,
|
|
832
|
+
lineText,
|
|
833
|
+
currentLineLength,
|
|
834
|
+
currentLineDropped,
|
|
835
|
+
currentLineDropped || currentLineLength > ringBudgetBytes ? capturePartialPreview() : undefined,
|
|
836
|
+
);
|
|
825
837
|
}
|
|
826
838
|
}
|
|
827
839
|
|
|
@@ -308,6 +308,7 @@ export function formatDiagnostics(
|
|
|
308
308
|
// Count total diagnostics for "... X more" calculation
|
|
309
309
|
const totalParsedDiags = files.reduce((sum, [, diags]) => sum + diags.length, 0);
|
|
310
310
|
const totalDiags = totalParsedDiags + unparsed.length;
|
|
311
|
+
const willShowAllRemaining = totalDiags <= maxDiags;
|
|
311
312
|
|
|
312
313
|
// Helper to check if this is the very last item in the tree
|
|
313
314
|
const isTreeEnd = (fileIdx: number, diagIdx: number | null, unparsedIdx: number | null): boolean => {
|
|
@@ -329,9 +330,6 @@ export function formatDiagnostics(
|
|
|
329
330
|
for (let fi = 0; fi < files.length && diagsShown < maxDiags; fi++) {
|
|
330
331
|
const [filePath, diagnostics] = files[fi];
|
|
331
332
|
// File is "last" only if no more files AND no unparsed AND we'll show all diags AND no "... X more"
|
|
332
|
-
const remainingDiagsInFile = diagnostics.length;
|
|
333
|
-
const remainingDiagsAfter = files.slice(fi + 1).reduce((sum, [, d]) => sum + d.length, 0) + unparsed.length;
|
|
334
|
-
const willShowAllRemaining = diagsShown + remainingDiagsInFile + remainingDiagsAfter <= maxDiags;
|
|
335
333
|
const isLastFileNode = fi === files.length - 1 && unparsed.length === 0 && willShowAllRemaining;
|
|
336
334
|
const fileBranch = isLastFileNode ? theme.tree.last : theme.tree.branch;
|
|
337
335
|
|
package/src/tools/resource-gc.ts
CHANGED
|
@@ -106,7 +106,10 @@ const defaultDeps: ResourceGcDeps = {
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
// ── Controller state (process-global; tabs/browsers are module-global too) ──────────────────
|
|
109
|
-
const activeSessions = new Map<
|
|
109
|
+
const activeSessions = new Map<
|
|
110
|
+
string,
|
|
111
|
+
{ settings: Settings; cwd: () => string; memoryGuardGeneration: symbol; memoryGuardEnabled: boolean }
|
|
112
|
+
>();
|
|
110
113
|
const scheduler = new MemoryGuardHost({
|
|
111
114
|
run: async () => {
|
|
112
115
|
await sweepOnce(deps);
|
|
@@ -141,12 +144,26 @@ function resolveSessionSweepIntervalMs(settings: Settings): number {
|
|
|
141
144
|
export function registerResourceGcSession(reg: ResourceGcRegistration): () => void {
|
|
142
145
|
const registeredCwd = reg.cwd;
|
|
143
146
|
const cwd = typeof registeredCwd === "function" ? registeredCwd : () => registeredCwd ?? process.cwd();
|
|
144
|
-
|
|
147
|
+
const registration = {
|
|
148
|
+
settings: reg.settings,
|
|
149
|
+
cwd,
|
|
150
|
+
memoryGuardGeneration: Symbol(),
|
|
151
|
+
memoryGuardEnabled: resolveMemoryGuardPolicy(reg.settings).enabled,
|
|
152
|
+
};
|
|
153
|
+
activeSessions.set(reg.sessionId, registration);
|
|
145
154
|
const unregisterSchedule = scheduler.register({
|
|
146
155
|
ownerId: reg.sessionId,
|
|
147
156
|
intervalMs: resolveSessionSweepIntervalMs(reg.settings),
|
|
148
157
|
});
|
|
149
158
|
const unregisterSettings = reg.settings.onChanged(path => {
|
|
159
|
+
if (path === "memoryGuard.enabled") {
|
|
160
|
+
const enabled = resolveMemoryGuardPolicy(reg.settings).enabled;
|
|
161
|
+
if (enabled !== registration.memoryGuardEnabled) {
|
|
162
|
+
// Fence samples already in flight, even if the guard is re-enabled before they settle.
|
|
163
|
+
registration.memoryGuardGeneration = Symbol();
|
|
164
|
+
registration.memoryGuardEnabled = enabled;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
150
167
|
if (
|
|
151
168
|
path === "memoryGuard.enabled" ||
|
|
152
169
|
path === "memoryGuard.checkIntervalMs" ||
|
|
@@ -498,8 +515,8 @@ function sweepMemoryPressureGuard(d: ResourceGcDeps): Promise<void> | undefined
|
|
|
498
515
|
}
|
|
499
516
|
async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void> {
|
|
500
517
|
const now = d.monotonicNow();
|
|
501
|
-
const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string }> = [];
|
|
502
|
-
for (const [sessionId, { settings, cwd: resolveCwd }] of activeSessions) {
|
|
518
|
+
const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string; generation: symbol }> = [];
|
|
519
|
+
for (const [sessionId, { settings, cwd: resolveCwd, memoryGuardGeneration }] of activeSessions) {
|
|
503
520
|
const policy = resolveMemoryGuardPolicy(settings);
|
|
504
521
|
const cwd = resolveCwd();
|
|
505
522
|
if (!policy.enabled) {
|
|
@@ -511,14 +528,15 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
511
528
|
const lastEvaluated = memoryGuardLastEvaluatedAt.get(sessionId);
|
|
512
529
|
if (lastEvaluated !== undefined && now - lastEvaluated < policy.checkIntervalMs) continue;
|
|
513
530
|
memoryGuardLastEvaluatedAt.set(sessionId, now);
|
|
514
|
-
dueSessions.push({ sessionId, policy, cwd });
|
|
531
|
+
dueSessions.push({ sessionId, policy, cwd, generation: memoryGuardGeneration });
|
|
515
532
|
}
|
|
516
533
|
if (dueSessions.length === 0) return;
|
|
517
534
|
|
|
518
535
|
const snapshot = await d.memorySnapshot();
|
|
519
536
|
let gcRequested = false;
|
|
520
|
-
const gcTelemetry: Array<{ sessionId: string } & Record<string, unknown>> = [];
|
|
521
|
-
for (const { sessionId, policy } of dueSessions) {
|
|
537
|
+
const gcTelemetry: Array<{ sessionId: string; generation: symbol } & Record<string, unknown>> = [];
|
|
538
|
+
for (const { sessionId, policy, generation } of dueSessions) {
|
|
539
|
+
if (activeSessions.get(sessionId)?.memoryGuardGeneration !== generation) continue;
|
|
522
540
|
const pressure = __selectMemoryPressureDomainForTest(snapshot, policy.policyLimitBytes);
|
|
523
541
|
const limit = resolveEffectiveMemoryLimit({
|
|
524
542
|
hardCapBytes: pressure.hardCapBytes,
|
|
@@ -548,6 +566,7 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
548
566
|
gcRequested = true;
|
|
549
567
|
gcTelemetry.push({
|
|
550
568
|
sessionId,
|
|
569
|
+
generation,
|
|
551
570
|
parentBytes: pressure.parentBytes,
|
|
552
571
|
totalUsageBytes: pressure.totalUsageBytes,
|
|
553
572
|
effectiveLimitBytes: limit.effectiveBytes,
|
|
@@ -587,7 +606,8 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
|
|
|
587
606
|
if (gcRequested) {
|
|
588
607
|
try {
|
|
589
608
|
d.runGc();
|
|
590
|
-
for (const { sessionId, ...telemetry } of gcTelemetry) {
|
|
609
|
+
for (const { sessionId, generation, ...telemetry } of gcTelemetry) {
|
|
610
|
+
if (activeSessions.get(sessionId)?.memoryGuardGeneration !== generation) continue;
|
|
591
611
|
memoryGuardGcActive.add(sessionId);
|
|
592
612
|
d.logWarn("Memory guard: GC threshold reached", { sessionId, ...telemetry });
|
|
593
613
|
}
|
|
@@ -76,9 +76,14 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
|
|
|
76
76
|
): Promise<AgentToolResult<SkillDiscoveryToolDetails>> {
|
|
77
77
|
return untilAborted(signal, async () => {
|
|
78
78
|
const source = input.source ?? "all";
|
|
79
|
+
const agentDir =
|
|
80
|
+
this.#session.getSessionAgentDir?.() ??
|
|
81
|
+
(this.#session.home === undefined ? this.#session.settings.getAgentDir() : undefined);
|
|
79
82
|
const result = await discoverRuntimeSkills({
|
|
80
83
|
cwd: this.#session.cwd,
|
|
81
84
|
home: this.#session.home,
|
|
85
|
+
agentDir,
|
|
86
|
+
profileAuthority: this.#session.profileAuthority,
|
|
82
87
|
query: input.query,
|
|
83
88
|
source,
|
|
84
89
|
limit: input.limit,
|
package/src/tools/skill.ts
CHANGED
|
@@ -32,6 +32,10 @@ import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
|
|
|
32
32
|
import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
|
|
33
33
|
import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
|
|
34
34
|
import { isCanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
35
|
+
import {
|
|
36
|
+
getSkillFilesystemIdentity,
|
|
37
|
+
isCanonicalGjcWorkflowSkillFilesystemAlias,
|
|
38
|
+
} from "../skill-state/canonical-skills";
|
|
35
39
|
import type { ToolSession } from ".";
|
|
36
40
|
import { ToolError } from "./tool-errors";
|
|
37
41
|
|
|
@@ -157,6 +161,12 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
157
161
|
`skill tool: "${requestedName}" is not a valid skill name. The name must be a single concrete skill (e.g. "ralplan"), not a glob or wildcard pattern. Pass one exact skill name as shown in /skill:<name>.`,
|
|
158
162
|
);
|
|
159
163
|
}
|
|
164
|
+
if (
|
|
165
|
+
isCanonicalGjcWorkflowSkillFilesystemAlias(requestedName) &&
|
|
166
|
+
getSkillFilesystemIdentity(requestedName) !== requestedName.toLowerCase()
|
|
167
|
+
) {
|
|
168
|
+
throw new ToolError(`skill tool: refusing filesystem alias for bundled workflow skill "${requestedName}"`);
|
|
169
|
+
}
|
|
160
170
|
const activeState = this.#session.getActiveSkillState?.();
|
|
161
171
|
const activeSkill = normalizeSkillName(activeState?.skill);
|
|
162
172
|
if (activeSkill && requestedName === activeSkill) {
|
|
@@ -165,6 +175,9 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
165
175
|
);
|
|
166
176
|
}
|
|
167
177
|
|
|
178
|
+
const agentDir =
|
|
179
|
+
this.#session.getSessionAgentDir?.() ??
|
|
180
|
+
(this.#session.home === undefined ? this.#session.settings.getAgentDir() : undefined);
|
|
168
181
|
const skill =
|
|
169
182
|
skills.find(s => s.name === requestedName) ??
|
|
170
183
|
(await findRuntimeSkillByName(
|
|
@@ -172,6 +185,8 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
172
185
|
requestedName,
|
|
173
186
|
this.#getRuntimeSkillPolicy(),
|
|
174
187
|
this.#session.home,
|
|
188
|
+
agentDir,
|
|
189
|
+
this.#session.profileAuthority,
|
|
175
190
|
));
|
|
176
191
|
if (!skill) {
|
|
177
192
|
const available = formatAvailableSkills(skills);
|
package/src/tools/subagent.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { ToolError } from "./tool-errors";
|
|
|
12
12
|
|
|
13
13
|
const DEFAULT_AWAIT_TIMEOUT_MS = 30_000;
|
|
14
14
|
const MAX_AWAIT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
15
|
+
const MAX_AWAIT_HEARTBEAT_MS = 60_000;
|
|
15
16
|
/**
|
|
16
17
|
* Bounded cadence for await liveness: even when the rendered-state signature is
|
|
17
18
|
* stable (a running subagent emitting no new progress), the await panel re-emits
|
|
@@ -47,9 +48,9 @@ const subagentSchema = z.object({
|
|
|
47
48
|
.describe("terminal wait condition; defaults to all_terminal"),
|
|
48
49
|
heartbeat_ms: z
|
|
49
50
|
.number()
|
|
50
|
-
.refine(value => value === 0 || (Number.isInteger(value) && value >= 100 && value <=
|
|
51
|
+
.refine(value => value === 0 || (Number.isInteger(value) && value >= 100 && value <= MAX_AWAIT_HEARTBEAT_MS))
|
|
51
52
|
.optional()
|
|
52
|
-
.describe(
|
|
53
|
+
.describe(`heartbeat interval in milliseconds (100-${MAX_AWAIT_HEARTBEAT_MS}); 0 disables`),
|
|
53
54
|
timeout_ms: z.number().min(0).max(MAX_AWAIT_TIMEOUT_MS).optional().describe("await timeout in milliseconds"),
|
|
54
55
|
limit: z.number().min(1).max(MAX_LIST_LIMIT).optional().describe("maximum subagents to return"),
|
|
55
56
|
verbosity: z
|
|
@@ -2243,7 +2243,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2243
2243
|
"subagent": {
|
|
2244
2244
|
"name": "subagent",
|
|
2245
2245
|
"label": "Subagent",
|
|
2246
|
-
"description": "Lists, inspects, awaits, pauses, resumes, steers, or cancels detached task subagents.\n\nTask launches return immediately. Use this tool when you need direct control over those running subagents. Prefer `subagent` for task subagents; generic `job` remains available for non-subagent jobs and compatibility fallback access.\n\n`verbosity` controls output size: `receipt` (default) returns status metadata plus a single ≤280-character result/error preview and an `agent://<id>` output ref when available; `preview` returns ≤2000 characters; `full` returns ≤12000 characters and requires explicit `ids`.\n\n# Operations\n\n## `action: \"list\"`\nSnapshot your visible detached subagents, including `running`, `paused`, `queued`, and terminal subagents when retained. Optional `limit` (1–50) caps how many subagents are returned. Output is receipt-only by default; use `verbosity: \"preview\"` for a bounded preview or inspect explicit `ids` with `verbosity: \"full\"` when fuller retained text is necessary.\n\n## `action: \"inspect\"`\nInspect selected subagents by `ids`; omit `ids` to inspect current running subagents. Terminal subagents return receipt-only output by default, with an `agent://<id>` ref when a verified output artifact is available. `verbosity: \"full\"` requires explicit `ids`.\n\n## `action: \"await\"`\nWait for selected subagents by `ids`; omit `ids` to wait for current running subagents.\n- Always set `timeout_ms` when the result is not immediately required forever.\n- Await timeout only bounds this tool call's wait; it does not stop the subagent and is not a failure reason.\n- On timeout, inspect progress and keep doing independent work. Never cancel just because an await timed out; cancel only if the subagent has actually failed, gone off-track, or become unrecoverably wrong.\n- Completed results are receipt-first by default: bounded preview plus `agent://<id>` output ref when available, not full retained output.\n\n## `action: \"pause\"`\nRequest a graceful safe-boundary pause for selected subagents by `ids`.\n- Non-running subagents are a no-op and return their current status snapshot.\n- A paused subagent keeps its session context and can be resumed later.\n\n## `action: \"resume\"`\nResume one subagent by `id` (preferred) or a single-item `ids` array.\n- Optional `message` is delivered into that one resumed run.\n- Running subagents are a no-op and return their current status snapshot.\n- Terminal subagents require `message` to start a follow-up resume run; without `message`, the tool returns the current snapshot with guidance.\n- `paused` subagents resume from saved context; `queued` subagents are already waiting for capacity.\n- Multiple targets are rejected because one global `message` must not broadcast to several subagents.\n\n## `action: \"steer\"`\nSend a non-empty `message` to one subagent by `id` (preferred) or a single-item `ids` array.\n- A running subagent receives the message through its live handle.\n- Optional `pause: true` requests a safe-boundary pause after steering a running subagent.\n- `pause` only matters while the target is running.\n- A non-active subagent (`paused`, `queued`, or terminal) automatically resumes with the message; `pause` is ignored for that target.\n- Multiple targets are rejected because one global `message` must not broadcast to several subagents.\n\n## `action: \"cancel\"`\nStop selected subagents by `ids`, including running, paused, or queued subagents.\n- Use only when the subagent has actually failed, gone off-track, or become unrecoverably wrong; an await timeout alone is never a cancellation reason.\n- Cancellation keeps the subagent session file for possible later context recovery.\n\n# Statuses\n\n- `running` — currently executing.\n- `paused` — stopped at a safe boundary with resumable context.\n- `queued` — resume requested and waiting for execution capacity.\n- `completed` — finished successfully.\n- `failed` — finished with an error.\n- `cancelled` — stopped by cancellation.\n- `not_found` — no visible subagent matches the requested id.",
|
|
2246
|
+
"description": "Lists, inspects, awaits, pauses, resumes, steers, or cancels detached task subagents.\n\nTask launches return immediately. Use this tool when you need direct control over those running subagents. Prefer `subagent` for task subagents; generic `job` remains available for non-subagent jobs and compatibility fallback access.\n\n`verbosity` controls output size: `receipt` (default) returns status metadata plus a single ≤280-character result/error preview and an `agent://<id>` output ref when available; `preview` returns ≤2000 characters; `full` returns ≤12000 characters and requires explicit `ids`.\n\n# Operations\n\n## `action: \"list\"`\nSnapshot your visible detached subagents, including `running`, `paused`, `queued`, and terminal subagents when retained. Optional `limit` (1–50) caps how many subagents are returned. Output is receipt-only by default; use `verbosity: \"preview\"` for a bounded preview or inspect explicit `ids` with `verbosity: \"full\"` when fuller retained text is necessary.\n\n## `action: \"inspect\"`\nInspect selected subagents by `ids`; omit `ids` to inspect current running subagents. Terminal subagents return receipt-only output by default, with an `agent://<id>` ref when a verified output artifact is available. `verbosity: \"full\"` requires explicit `ids`.\n\n## `action: \"await\"`\nWait for selected subagents by `ids`; omit `ids` to wait for current running subagents.\n- Always set `timeout_ms` when the result is not immediately required forever.\n- `heartbeat_ms` accepts integer intervals from 100 to 60,000 milliseconds (for example, 10,000 for ten seconds); `0` disables heartbeat updates and omission retains the 500 ms default. Terminal completion is event-driven, not delayed until the next heartbeat.\n- Await timeout only bounds this tool call's wait; it does not stop the subagent and is not a failure reason.\n- On timeout, inspect progress and keep doing independent work. Never cancel just because an await timed out; cancel only if the subagent has actually failed, gone off-track, or become unrecoverably wrong.\n- Completed results are receipt-first by default: bounded preview plus `agent://<id>` output ref when available, not full retained output.\n\n## `action: \"pause\"`\nRequest a graceful safe-boundary pause for selected subagents by `ids`.\n- Non-running subagents are a no-op and return their current status snapshot.\n- A paused subagent keeps its session context and can be resumed later.\n\n## `action: \"resume\"`\nResume one subagent by `id` (preferred) or a single-item `ids` array.\n- Optional `message` is delivered into that one resumed run.\n- Running subagents are a no-op and return their current status snapshot.\n- Terminal subagents require `message` to start a follow-up resume run; without `message`, the tool returns the current snapshot with guidance.\n- `paused` subagents resume from saved context; `queued` subagents are already waiting for capacity.\n- Multiple targets are rejected because one global `message` must not broadcast to several subagents.\n\n## `action: \"steer\"`\nSend a non-empty `message` to one subagent by `id` (preferred) or a single-item `ids` array.\n- A running subagent receives the message through its live handle.\n- Optional `pause: true` requests a safe-boundary pause after steering a running subagent.\n- `pause` only matters while the target is running.\n- A non-active subagent (`paused`, `queued`, or terminal) automatically resumes with the message; `pause` is ignored for that target.\n- Multiple targets are rejected because one global `message` must not broadcast to several subagents.\n\n## `action: \"cancel\"`\nStop selected subagents by `ids`, including running, paused, or queued subagents.\n- Use only when the subagent has actually failed, gone off-track, or become unrecoverably wrong; an await timeout alone is never a cancellation reason.\n- Cancellation keeps the subagent session file for possible later context recovery.\n\n# Statuses\n\n- `running` — currently executing.\n- `paused` — stopped at a safe boundary with resumable context.\n- `queued` — resume requested and waiting for execution capacity.\n- `completed` — finished successfully.\n- `failed` — finished with an error.\n- `cancelled` — stopped by cancellation.\n- `not_found` — no visible subagent matches the requested id.",
|
|
2247
2247
|
"parameters": {
|
|
2248
2248
|
"type": "object",
|
|
2249
2249
|
"properties": {
|
|
@@ -2288,7 +2288,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2288
2288
|
]
|
|
2289
2289
|
},
|
|
2290
2290
|
"heartbeat_ms": {
|
|
2291
|
-
"description": "heartbeat interval; 0 disables",
|
|
2291
|
+
"description": "heartbeat interval in milliseconds (100-60000); 0 disables",
|
|
2292
2292
|
"type": "number"
|
|
2293
2293
|
},
|
|
2294
2294
|
"timeout_ms": {
|
|
@@ -70,9 +70,9 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
|
70
70
|
const candidate = tool as GuardedTool;
|
|
71
71
|
if (candidate[ULTRAGOAL_ASK_GUARD]) return tool;
|
|
72
72
|
const wrapped = new Proxy(tool, {
|
|
73
|
-
get(target, prop
|
|
73
|
+
get(target, prop) {
|
|
74
74
|
if (prop === ULTRAGOAL_ASK_GUARD) return true;
|
|
75
|
-
if (prop !== "execute") return Reflect.get(target, prop
|
|
75
|
+
if (prop !== "execute") return Reflect.get(target, prop);
|
|
76
76
|
return async (...args: unknown[]): Promise<unknown> => {
|
|
77
77
|
// The wrapper runs BEFORE AskTool.execute(): resolve the nudge
|
|
78
78
|
// budget against the SESSION profile, never the process-global one.
|