@gajae-code/coding-agent 0.18.10 → 0.18.12
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 +295 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +14 -1
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +5 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/config/autorouting-tier-map.d.ts +30 -0
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +39 -0
- package/dist/types/config/file-lock.d.ts +8 -1
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -6
- package/dist/types/config/model-registry.d.ts +56 -5
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +46 -3
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/modes/patch.d.ts +5 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +10 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/types.d.ts +15 -3
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
- package/dist/types/sdk/broker/broker.d.ts +7 -1
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +136 -7
- package/dist/types/sdk/broker/hop.d.ts +36 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +4 -1
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +23 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.d.ts +32 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +58 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +13 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
- package/dist/types/tools/descriptors.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +22 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +27 -19
- package/src/capability/index.ts +2 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +7 -5
- package/src/cli/public-command-errors.ts +4 -0
- package/src/cli/root-flags.ts +2 -2
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +224 -7
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +199 -42
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/message.ts +10 -3
- package/src/config/autorouting-tier-map.ts +30 -0
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +734 -0
- package/src/config/file-lock.ts +216 -56
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +716 -113
- package/src/config/model-registry.ts +1334 -158
- package/src/config/model-resolver.ts +3 -1
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +60 -3
- package/src/config/settings.ts +458 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/crash/sanitize.ts +4 -0
- package/src/customization/import.ts +3 -1
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/debug/crash-diagnostics.ts +2 -1
- package/src/debug/report-bundle.ts +19 -0
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/helpers.ts +1 -2
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +13 -17
- package/src/edit/modes/patch.ts +187 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/export/html/index.ts +46 -6
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +8 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/gjc-runtime/gc-runtime.ts +33 -6
- package/src/gjc-runtime/launch-tmux.ts +7 -2
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/hooks/native-skill-hook.ts +14 -11
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/local-protocol.ts +142 -8
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/migrate/action-planner.ts +57 -0
- package/src/migrate/adapters/claude-code.ts +23 -4
- package/src/migrate/adapters/codex.ts +22 -4
- package/src/migrate/adapters/index.ts +22 -4
- package/src/migrate/adapters/opencode.ts +6 -1
- package/src/migrate/executor.ts +84 -7
- package/src/migrate/report.ts +19 -2
- package/src/migrate/types.ts +14 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/controllers/command-controller.ts +38 -2
- package/src/modes/controllers/plan-mode-controller.ts +17 -7
- package/src/modes/controllers/selector-controller.ts +62 -6
- package/src/modes/interactive-mode.ts +68 -0
- package/src/modes/theme/theme.ts +15 -0
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +32 -0
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +126 -17
- package/src/sdk/broker/broker-exit.ts +8 -5
- package/src/sdk/broker/broker.ts +342 -82
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1143 -265
- package/src/sdk/broker/hop.ts +274 -0
- package/src/sdk/broker/lifecycle-ledger.ts +35 -11
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +62 -12
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/index.ts +52 -21
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/cli/session-cli.ts +21 -6
- package/src/sdk/host/session-runtime.test.ts +118 -23
- package/src/sdk/host/session-runtime.ts +352 -47
- package/src/sdk/lifecycle/service.ts +1 -0
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/protocol/adapter-validation.ts +53 -11
- package/src/sdk/protocol/operation-inventory.generated.json +101 -2
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +114 -61
- package/src/session/agent-session.ts +1043 -81
- package/src/session/internal/managed-session-scope.ts +126 -39
- package/src/session/internal/managed-session-storage.ts +14 -11
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +236 -14
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +491 -32
- package/src/slash-commands/builtin-registry.ts +15 -3
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/task/executor.ts +103 -12
- package/src/task/index.ts +389 -175
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +3 -0
- package/src/tools/bash-allowed-prefixes.ts +153 -5
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash.ts +14 -0
- package/src/tools/debug.ts +7 -6
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +34 -2
- package/src/tools/fetch.ts +3 -2
- package/src/tools/index.ts +2 -0
- package/src/tools/output-meta.ts +23 -2
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +17 -35
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +108 -1
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +31 -3
- package/src/tools/write.ts +35 -3
- package/src/utils/commit-message-generator.ts +9 -2
- package/src/utils/file-mentions.ts +29 -1
- package/src/utils/title-generator.ts +7 -2
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import type { AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
2
3
|
|
|
3
4
|
import { sanitizeText } from "@gajae-code/utils";
|
|
@@ -28,6 +29,78 @@ const NL = "\n";
|
|
|
28
29
|
|
|
29
30
|
const ELLIPSIS = "…";
|
|
30
31
|
|
|
32
|
+
// Bound the temporary parts/join allocation in #applyColumnCap for a single
|
|
33
|
+
// large callback. Keep batches small enough to avoid per-line retention work.
|
|
34
|
+
const COLUMN_CAP_BATCH_CHARS = 64 * 1024;
|
|
35
|
+
|
|
36
|
+
const outputPlainSecrets = new AsyncLocalStorage<readonly string[]>();
|
|
37
|
+
|
|
38
|
+
/** Run `fn` while column caps can see these plain secrets. */
|
|
39
|
+
export function runWithOutputRedaction<T>(plainSecrets: readonly string[], fn: () => T): T {
|
|
40
|
+
return outputPlainSecrets.run(plainSecrets, fn);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Get the active plain secrets from AsyncLocalStorage, if any are set. */
|
|
44
|
+
function activePlainSecrets(): readonly string[] | undefined {
|
|
45
|
+
const secrets = outputPlainSecrets.getStore();
|
|
46
|
+
return secrets && secrets.length > 0 ? secrets : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Pull a column-cap end back to the start of any plain secret that crosses it.
|
|
51
|
+
* A secret that ends on the cut stays, so a later whole-string obfuscator can match it.
|
|
52
|
+
*/
|
|
53
|
+
function columnHeadEndOutsideSecrets(segment: string, end: number, secrets: readonly string[]): number {
|
|
54
|
+
let cut = end;
|
|
55
|
+
while (cut > 0) {
|
|
56
|
+
let earliest = cut;
|
|
57
|
+
for (const secret of secrets) {
|
|
58
|
+
const secretChars = secret.length;
|
|
59
|
+
if (secretChars === 0) continue;
|
|
60
|
+
const windowStart = Math.max(0, cut - secretChars);
|
|
61
|
+
const window = segment.slice(windowStart, Math.min(segment.length, cut + secretChars));
|
|
62
|
+
const limit = cut - windowStart;
|
|
63
|
+
let from = 0;
|
|
64
|
+
while (from < limit) {
|
|
65
|
+
const rel = window.indexOf(secret, from);
|
|
66
|
+
if (rel === -1 || rel >= limit) break;
|
|
67
|
+
if (rel + secretChars > limit) earliest = Math.min(earliest, windowStart + rel);
|
|
68
|
+
from = rel + 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (earliest === cut) break;
|
|
72
|
+
cut = earliest;
|
|
73
|
+
}
|
|
74
|
+
return cut;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Pull a column-cap end off a proper prefix of a plain secret.
|
|
79
|
+
* Each candidate's continuation is checked from longest to shortest.
|
|
80
|
+
* A longer candidate that is already finished or contradicted does not hide
|
|
81
|
+
* a shorter open prefix. A secret that already ends on the cut stays.
|
|
82
|
+
*/
|
|
83
|
+
function columnHeadEndBeforeOpenPrefix(text: string, end: number, secrets: readonly string[]): number {
|
|
84
|
+
let cut = end;
|
|
85
|
+
const after = text.slice(end);
|
|
86
|
+
for (const secret of secrets) {
|
|
87
|
+
const secretChars = secret.length;
|
|
88
|
+
if (secretChars < 2 || end < 1) continue;
|
|
89
|
+
if (end >= secretChars && text.slice(end - secretChars, end) === secret) continue;
|
|
90
|
+
const maxPrefix = Math.min(secretChars - 1, end);
|
|
91
|
+
for (let k = maxPrefix; k >= 1; k--) {
|
|
92
|
+
if (text.slice(end - k, end) !== secret.slice(0, k)) continue;
|
|
93
|
+
const rest = secret.slice(k);
|
|
94
|
+
const have = Math.min(after.length, rest.length);
|
|
95
|
+
if (have === rest.length) continue;
|
|
96
|
+
if (after.slice(0, have) !== rest.slice(0, have)) continue;
|
|
97
|
+
cut = Math.min(cut, end - k);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return cut;
|
|
102
|
+
}
|
|
103
|
+
|
|
31
104
|
const ARTIFACT_WRITER_DIAGNOSTIC_MAX_BYTES = 256;
|
|
32
105
|
|
|
33
106
|
/**
|
|
@@ -314,6 +387,72 @@ export function truncateLine(
|
|
|
314
387
|
return { text: `${line.slice(0, maxChars)}…`, wasTruncated: true };
|
|
315
388
|
}
|
|
316
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Truncate each line to max characters without leaving a configured plain-secret
|
|
392
|
+
* prefix in the retained text. Prior uncapped lines remain in scope because a
|
|
393
|
+
* later line's cut can split a multiline secret.
|
|
394
|
+
*/
|
|
395
|
+
export function truncateTextWithSecretAwareness(
|
|
396
|
+
text: string,
|
|
397
|
+
maxChars: number = DEFAULT_MAX_COLUMN,
|
|
398
|
+
secrets?: readonly string[],
|
|
399
|
+
): { text: string; wasTruncated: boolean } {
|
|
400
|
+
const lines = text.split(NL);
|
|
401
|
+
const activeSecrets = secrets ?? activePlainSecrets();
|
|
402
|
+
if (!activeSecrets?.some(secret => secret.length > 0)) {
|
|
403
|
+
let wasTruncated = false;
|
|
404
|
+
const truncated = lines.map(line => {
|
|
405
|
+
const result = truncateLine(line, maxChars);
|
|
406
|
+
wasTruncated ||= result.wasTruncated;
|
|
407
|
+
return result.text;
|
|
408
|
+
});
|
|
409
|
+
return { text: truncated.join(NL), wasTruncated };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const contextChars = activeSecrets.reduce((max, secret) => Math.max(max, secret.length), 0);
|
|
413
|
+
let context = "";
|
|
414
|
+
const outputParts: string[] = [];
|
|
415
|
+
let wasTruncated = false;
|
|
416
|
+
for (let index = 0; index < lines.length; index++) {
|
|
417
|
+
const line = lines[index] ?? "";
|
|
418
|
+
const newline = index < lines.length - 1 ? NL : "";
|
|
419
|
+
if (line.length <= maxChars) {
|
|
420
|
+
outputParts.push(line, newline);
|
|
421
|
+
context = (context + line + newline).slice(-contextChars);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
wasTruncated = true;
|
|
426
|
+
const combined = context + line;
|
|
427
|
+
let end = context.length + maxChars;
|
|
428
|
+
while (end > 0) {
|
|
429
|
+
const next = Math.min(
|
|
430
|
+
columnHeadEndOutsideSecrets(combined, end, activeSecrets),
|
|
431
|
+
columnHeadEndBeforeOpenPrefix(combined, end, activeSecrets),
|
|
432
|
+
);
|
|
433
|
+
if (next >= end) break;
|
|
434
|
+
end = next;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (end < context.length) {
|
|
438
|
+
const retract = context.slice(end);
|
|
439
|
+
let output = outputParts.join("");
|
|
440
|
+
if (output.endsWith(retract)) {
|
|
441
|
+
output = output.slice(0, output.length - retract.length);
|
|
442
|
+
output += NL.repeat(countNewlines(retract));
|
|
443
|
+
} else {
|
|
444
|
+
output = NL.repeat(countNewlines(output));
|
|
445
|
+
}
|
|
446
|
+
outputParts.length = 0;
|
|
447
|
+
outputParts.push(output);
|
|
448
|
+
}
|
|
449
|
+
const keptChars = Math.max(0, end - context.length);
|
|
450
|
+
outputParts.push(line.slice(0, keptChars), ELLIPSIS, newline);
|
|
451
|
+
context = "";
|
|
452
|
+
}
|
|
453
|
+
return { text: outputParts.join(""), wasTruncated };
|
|
454
|
+
}
|
|
455
|
+
|
|
317
456
|
// =============================================================================
|
|
318
457
|
// Content truncation (line + byte aware, no full Buffer allocation)
|
|
319
458
|
// =============================================================================
|
|
@@ -952,6 +1091,13 @@ export class OutputSink {
|
|
|
952
1091
|
#columnEllipsisAdded = false;
|
|
953
1092
|
#columnDroppedBytes = 0;
|
|
954
1093
|
#columnTruncatedLines = 0;
|
|
1094
|
+
/** Plain secrets captured when this sink is built inside an active redaction scope. */
|
|
1095
|
+
readonly #plainSecrets?: readonly string[];
|
|
1096
|
+
readonly #plainSecretContextChars: number;
|
|
1097
|
+
/** Text already retained for the open line. A later chunk may shorten it. */
|
|
1098
|
+
#columnLine = "";
|
|
1099
|
+
/** Bounded suffix of prior uncapped output, preserved for multiline secrets. */
|
|
1100
|
+
#columnLineContext = "";
|
|
955
1101
|
#file?: {
|
|
956
1102
|
path: string;
|
|
957
1103
|
artifactId?: string;
|
|
@@ -1010,6 +1156,8 @@ export class OutputSink {
|
|
|
1010
1156
|
this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
|
|
1011
1157
|
this.#artifactPublisher = artifactPublisher;
|
|
1012
1158
|
this.#coalesceSanitize = coalesceSanitize;
|
|
1159
|
+
this.#plainSecrets = activePlainSecrets();
|
|
1160
|
+
this.#plainSecretContextChars = this.#plainSecrets?.reduce((max, secret) => Math.max(max, secret.length), 0) ?? 0;
|
|
1013
1161
|
}
|
|
1014
1162
|
|
|
1015
1163
|
#headText(): string {
|
|
@@ -1104,10 +1252,45 @@ export class OutputSink {
|
|
|
1104
1252
|
|
|
1105
1253
|
if (rawBytes === 0) return;
|
|
1106
1254
|
|
|
1107
|
-
const visibleChunk = this.#maxColumns > 0 ? this.#applyColumnCap(sanitizedChunk) : sanitizedChunk;
|
|
1108
1255
|
if (this.#artifactPath && this.#maxColumns > 0) this.#enqueueFileWrite(rawChunk, rawBytes);
|
|
1109
1256
|
else if (this.#artifactPublisher && this.#maxColumns > 0) this.#enqueueTerminalArtifactChunk(rawChunk, rawBytes);
|
|
1257
|
+
|
|
1258
|
+
if (this.#maxColumns > 0) {
|
|
1259
|
+
if (this.#plainSecrets) {
|
|
1260
|
+
// Secret-aware cuts can retract text from an earlier line, so retain
|
|
1261
|
+
// each line before processing the next one.
|
|
1262
|
+
let cursor = 0;
|
|
1263
|
+
while (cursor < sanitizedChunk.length) {
|
|
1264
|
+
const newline = sanitizedChunk.indexOf(NL, cursor);
|
|
1265
|
+
const end = newline === -1 ? sanitizedChunk.length : newline + 1;
|
|
1266
|
+
this.#retainVisible(this.#applyColumnCap(sanitizedChunk.slice(cursor, end)));
|
|
1267
|
+
cursor = end;
|
|
1268
|
+
}
|
|
1269
|
+
} else {
|
|
1270
|
+
// Batch ordinary output while bounding #applyColumnCap's temporary
|
|
1271
|
+
// parts/join allocation for large single callbacks. Retaining each
|
|
1272
|
+
// line separately repeats accounting millions of times.
|
|
1273
|
+
let cursor = 0;
|
|
1274
|
+
while (cursor < sanitizedChunk.length) {
|
|
1275
|
+
let end = Math.min(cursor + COLUMN_CAP_BATCH_CHARS, sanitizedChunk.length);
|
|
1276
|
+
// UTF-8 byte accounting must not observe a surrogate pair as two
|
|
1277
|
+
// replacement characters when a batch boundary lands between them.
|
|
1278
|
+
if (end < sanitizedChunk.length) {
|
|
1279
|
+
const before = sanitizedChunk.charCodeAt(end - 1);
|
|
1280
|
+
const after = sanitizedChunk.charCodeAt(end);
|
|
1281
|
+
if (before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff) end--;
|
|
1282
|
+
}
|
|
1283
|
+
this.#retainVisible(this.#applyColumnCap(sanitizedChunk.slice(cursor, end)));
|
|
1284
|
+
cursor = end;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
this.#retainVisible(sanitizedChunk);
|
|
1289
|
+
}
|
|
1110
1290
|
if (this.#columnDroppedBytes > 0) this.#createFileSink();
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
#retainVisible(visibleChunk: string): void {
|
|
1111
1294
|
const visibleBytes = Buffer.byteLength(visibleChunk, "utf-8");
|
|
1112
1295
|
if (visibleChunk.length > 0) {
|
|
1113
1296
|
this.#processedBytes += visibleBytes;
|
|
@@ -1125,6 +1308,7 @@ export class OutputSink {
|
|
|
1125
1308
|
return;
|
|
1126
1309
|
}
|
|
1127
1310
|
const headSlice = truncateHeadBytes(visibleChunk, room);
|
|
1311
|
+
this.#headRetentionDisabled = true;
|
|
1128
1312
|
if (headSlice.bytes > 0) {
|
|
1129
1313
|
this.#appendHead(headSlice.text, headSlice.bytes);
|
|
1130
1314
|
tailChunk = visibleChunk.substring(headSlice.text.length);
|
|
@@ -1161,34 +1345,106 @@ export class OutputSink {
|
|
|
1161
1345
|
if (segBytes <= remaining) {
|
|
1162
1346
|
parts.push(segment);
|
|
1163
1347
|
this.#currentLineBytes += segBytes;
|
|
1348
|
+
if (this.#plainSecrets) this.#columnLine += segment;
|
|
1164
1349
|
} else {
|
|
1165
1350
|
// First overflow on this line: keep what fits, append ellipsis,
|
|
1166
|
-
// arm the skip-until-newline flag.
|
|
1351
|
+
// arm the skip-until-newline flag. With secrets, do not emit a
|
|
1352
|
+
// kept-head suffix that is still a prefix of a plain secret, and
|
|
1353
|
+
// do not end inside one the whole-string obfuscator must see whole.
|
|
1167
1354
|
const ellipsisBytes = 3; // "…" in UTF-8
|
|
1168
1355
|
const headRoom = Math.max(0, remaining - ellipsisBytes);
|
|
1169
1356
|
let keptBytes = 0;
|
|
1357
|
+
let keptText = "";
|
|
1170
1358
|
if (headRoom > 0) {
|
|
1171
1359
|
const sliced = truncateHeadBytes(segment, headRoom);
|
|
1360
|
+
keptText = sliced.text;
|
|
1172
1361
|
keptBytes = sliced.bytes;
|
|
1173
|
-
parts.push(sliced.text);
|
|
1174
1362
|
}
|
|
1363
|
+
const secrets = this.#plainSecrets;
|
|
1364
|
+
let lineBytesReset = false;
|
|
1365
|
+
if (secrets && segment.startsWith(keptText)) {
|
|
1366
|
+
const prior = this.#columnLine;
|
|
1367
|
+
const prefix = this.#columnLineContext + prior;
|
|
1368
|
+
const text = prefix + segment;
|
|
1369
|
+
const cut = prefix.length + keptText.length;
|
|
1370
|
+
let end = cut;
|
|
1371
|
+
while (end > 0) {
|
|
1372
|
+
const next = Math.min(
|
|
1373
|
+
columnHeadEndOutsideSecrets(text, end, secrets),
|
|
1374
|
+
columnHeadEndBeforeOpenPrefix(text, end, secrets),
|
|
1375
|
+
);
|
|
1376
|
+
if (next >= end) break;
|
|
1377
|
+
end = next;
|
|
1378
|
+
}
|
|
1379
|
+
if (end < cut) {
|
|
1380
|
+
if (end < prefix.length) {
|
|
1381
|
+
this.#retractColumnSuffix(prefix.slice(end));
|
|
1382
|
+
if (end < this.#columnLineContext.length) {
|
|
1383
|
+
this.#columnLineContext = this.#columnLineContext.slice(0, end);
|
|
1384
|
+
this.#columnLine = "";
|
|
1385
|
+
} else {
|
|
1386
|
+
this.#columnLine = prior.slice(0, end - this.#columnLineContext.length);
|
|
1387
|
+
}
|
|
1388
|
+
keptText = "";
|
|
1389
|
+
keptBytes = 0;
|
|
1390
|
+
} else {
|
|
1391
|
+
keptText = segment.slice(0, end - prefix.length);
|
|
1392
|
+
keptBytes = Buffer.byteLength(keptText, "utf-8");
|
|
1393
|
+
this.#columnLine = prior + keptText;
|
|
1394
|
+
}
|
|
1395
|
+
// columnLine already includes the bytes kept from this segment.
|
|
1396
|
+
this.#currentLineBytes = Buffer.byteLength(this.#columnLine, "utf-8");
|
|
1397
|
+
lineBytesReset = true;
|
|
1398
|
+
} else {
|
|
1399
|
+
this.#columnLine = prior + keptText;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (keptText.length > 0) parts.push(keptText);
|
|
1175
1403
|
parts.push(ELLIPSIS);
|
|
1176
1404
|
this.#columnDroppedBytes += segBytes - keptBytes;
|
|
1177
1405
|
this.#columnTruncatedLines++;
|
|
1178
|
-
this.#currentLineBytes += keptBytes + ellipsisBytes;
|
|
1406
|
+
this.#currentLineBytes += (lineBytesReset ? 0 : keptBytes) + ellipsisBytes;
|
|
1179
1407
|
this.#columnEllipsisAdded = true;
|
|
1180
1408
|
}
|
|
1181
1409
|
}
|
|
1182
1410
|
}
|
|
1183
1411
|
if (nlIdx === -1) break;
|
|
1184
1412
|
parts.push(NL);
|
|
1413
|
+
if (this.#plainSecretContextChars > 0 && !this.#columnEllipsisAdded) {
|
|
1414
|
+
const context = this.#columnLineContext + this.#columnLine + NL;
|
|
1415
|
+
this.#columnLineContext = context.slice(-this.#plainSecretContextChars);
|
|
1416
|
+
} else {
|
|
1417
|
+
this.#columnLineContext = "";
|
|
1418
|
+
}
|
|
1185
1419
|
this.#currentLineBytes = 0;
|
|
1186
1420
|
this.#columnEllipsisAdded = false;
|
|
1421
|
+
this.#columnLine = "";
|
|
1187
1422
|
cursor = nlIdx + 1;
|
|
1188
1423
|
}
|
|
1189
1424
|
return parts.join("");
|
|
1190
1425
|
}
|
|
1191
1426
|
|
|
1427
|
+
/** Remove a suffix of previously retained output after a secret-aware cut-back. */
|
|
1428
|
+
#retractColumnSuffix(suffix: string): void {
|
|
1429
|
+
if (suffix.length === 0) return;
|
|
1430
|
+
const bytes = Buffer.byteLength(suffix, "utf-8");
|
|
1431
|
+
const startByte = Math.max(0, this.#processedBytes - bytes);
|
|
1432
|
+
const tailStartByte = Math.max(0, this.#processedBytes - this.#bufferBytes);
|
|
1433
|
+
const keptTailBytes = Math.max(0, Math.min(this.#bufferBytes, startByte - tailStartByte));
|
|
1434
|
+
if (keptTailBytes < this.#bufferBytes) {
|
|
1435
|
+
const nextTail = truncateHeadBytes(this.#buffer, keptTailBytes);
|
|
1436
|
+
this.#setTail(nextTail.text, nextTail.bytes);
|
|
1437
|
+
}
|
|
1438
|
+
if (startByte < this.#headBytes) {
|
|
1439
|
+
const nextHead = truncateHeadBytes(this.#head, startByte);
|
|
1440
|
+
this.#head = nextHead.text;
|
|
1441
|
+
this.#headBytes = nextHead.bytes;
|
|
1442
|
+
}
|
|
1443
|
+
this.#processedBytes = Math.max(0, this.#processedBytes - bytes);
|
|
1444
|
+
this.#processedLines = Math.max(0, this.#processedLines - countNewlines(suffix));
|
|
1445
|
+
this.#columnDroppedBytes += bytes;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1192
1448
|
#retainedSummary(text: string): { text: string; bytes: number; lines: number } {
|
|
1193
1449
|
return { text, bytes: Buffer.byteLength(text, "utf-8"), lines: text.length > 0 ? countNewlines(text) : 0 };
|
|
1194
1450
|
}
|
|
@@ -1458,6 +1714,8 @@ export class OutputSink {
|
|
|
1458
1714
|
*/
|
|
1459
1715
|
replace(text: string): void {
|
|
1460
1716
|
this.#coalesceBuf = "";
|
|
1717
|
+
this.#columnLine = "";
|
|
1718
|
+
this.#columnLineContext = "";
|
|
1461
1719
|
const replacementBytes = Buffer.byteLength(text, "utf-8");
|
|
1462
1720
|
this.#head = "";
|
|
1463
1721
|
this.#headBytes = 0;
|
|
@@ -382,6 +382,16 @@ export function isOwnedAttemptRegistrationIncomplete(lineageIdHash: string, atte
|
|
|
382
382
|
return incompleteOwnedAttempts.has(`${lineageIdHash}\u0000${attemptEpoch}`);
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/** Move fail-closed saturation authority with all ownership state during endpoint rekey. */
|
|
386
|
+
export function migrateOwnedAttemptSaturationsForEndpoint(
|
|
387
|
+
predecessorEndpointId: string,
|
|
388
|
+
successorEndpointId: string,
|
|
389
|
+
): void {
|
|
390
|
+
for (const [attemptKey, endpointId] of saturatedAttemptEndpoints) {
|
|
391
|
+
if (endpointId === predecessorEndpointId) saturatedAttemptEndpoints.set(attemptKey, successorEndpointId);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
385
395
|
/** Exact (jobId, jobGeneration) lookup for completion-origin classification. */
|
|
386
396
|
export function lookupOwnedRegistration(
|
|
387
397
|
jobId: string,
|
|
@@ -841,6 +851,7 @@ export function registerOwnedIfLineaged(
|
|
|
841
851
|
toolCallId: string | undefined,
|
|
842
852
|
jobId: string,
|
|
843
853
|
endpointId?: string,
|
|
854
|
+
ownedRegistrationEndpointId?: string,
|
|
844
855
|
): void {
|
|
845
856
|
try {
|
|
846
857
|
let lineage = resolveToolLineage(toolCallId, endpointId);
|
|
@@ -860,9 +871,10 @@ export function registerOwnedIfLineaged(
|
|
|
860
871
|
const job = manager.getJob?.(jobId);
|
|
861
872
|
const jobGeneration = job?.generation;
|
|
862
873
|
if (!jobGeneration) return;
|
|
874
|
+
const registrationEndpointId = ownedRegistrationEndpointId ?? lineage.endpointId;
|
|
863
875
|
registerOwnedRegistration(
|
|
864
876
|
{
|
|
865
|
-
...(
|
|
877
|
+
...(registrationEndpointId ? { endpointId: registrationEndpointId } : {}),
|
|
866
878
|
endpointGeneration: lineage.endpointGeneration,
|
|
867
879
|
lineageIdHash: lineage.lineageIdHash,
|
|
868
880
|
promptAttemptEpoch: lineage.promptAttemptEpoch,
|
|
@@ -877,7 +889,7 @@ export function registerOwnedIfLineaged(
|
|
|
877
889
|
// job id bg_1) must not be judged terminal using THIS manager's
|
|
878
890
|
// unrelated bg_1 record (review thread P2).
|
|
879
891
|
isJobTerminal: candidate => {
|
|
880
|
-
if ((candidate.endpointId ?? "") !== (
|
|
892
|
+
if ((candidate.endpointId ?? "") !== (registrationEndpointId ?? "")) return undefined;
|
|
881
893
|
const status = manager.getJob?.(candidate.jobId)?.status;
|
|
882
894
|
return status === "completed" || status === "cancelled" || status === "failed" || status === "evicted";
|
|
883
895
|
},
|