@gajae-code/coding-agent 0.16.3 → 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 +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- 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/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -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/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- 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 +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -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 +14 -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/assistant-message.d.ts +1 -2
- 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/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -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-daemon-runtime.d.ts +12 -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/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- 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 +26 -1
- 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-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -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 +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- 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/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- 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 +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- 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 +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- 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/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- 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 +12 -12
- 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 +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- 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 +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- 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 +72 -6
- 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 +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- 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 +11 -8
- 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 +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- 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 +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- 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/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -3,7 +3,6 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentMessage } from "@gajae-code/agent-core";
|
|
5
5
|
import type { AssistantMessage, ToolResultMessage, UserMessage } from "@gajae-code/ai/core";
|
|
6
|
-
import { $ } from "bun";
|
|
7
6
|
import { resolveGjcCommand } from "../task/gjc-command";
|
|
8
7
|
import { shortenPath } from "../tools/render-utils";
|
|
9
8
|
|
|
@@ -12,6 +11,12 @@ export const CONTRIBUTION_PREP_SCHEMA_VERSION = 1;
|
|
|
12
11
|
const MAX_TRANSCRIPT_MESSAGES = 20;
|
|
13
12
|
const MAX_TEXT_CHARS = 12000;
|
|
14
13
|
const MAX_GIT_OUTPUT_CHARS = 60000;
|
|
14
|
+
const MAX_REDACTION_INPUT_CHARS = 1_000_000;
|
|
15
|
+
// Keep whole Git captures within the redactor's input budget; discard an overrun
|
|
16
|
+
// instead of emitting a prefix.
|
|
17
|
+
const MAX_GIT_RAW_OUTPUT_BYTES = MAX_REDACTION_INPUT_CHARS;
|
|
18
|
+
const MAX_JSON_TOKENS = 20_000;
|
|
19
|
+
const MAX_JSON_REPLACEMENTS = 10_000;
|
|
15
20
|
|
|
16
21
|
export interface ContributionPrepArtifact {
|
|
17
22
|
path: string;
|
|
@@ -72,12 +77,133 @@ function replaceRegex(text: string, regex: RegExp, replacement: string, state: R
|
|
|
72
77
|
return text.replace(regex, replacement);
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
function redactAwsAccessKeyIds(text: string, state: RedactionState): string {
|
|
81
|
+
return replaceRegex(
|
|
82
|
+
text,
|
|
83
|
+
/(^|[^0-9A-Za-z])(?:AKIA|ASIA)[0-9A-Z]{16}(?![0-9A-Za-z])/g,
|
|
84
|
+
"$1[REDACTED_AWS_KEY_ID]",
|
|
85
|
+
state,
|
|
86
|
+
"aws_keys",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isAwsSecretField(value: string): boolean {
|
|
91
|
+
const normalized = value.toLowerCase().replaceAll(/[_-]/g, "");
|
|
92
|
+
return (
|
|
93
|
+
normalized === "secretaccesskey" ||
|
|
94
|
+
normalized === "sessiontoken" ||
|
|
95
|
+
normalized === "awssecretaccesskey" ||
|
|
96
|
+
normalized === "awssessiontoken" ||
|
|
97
|
+
normalized === "xamzsecuritytoken"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function redactAwsLabeledValues(text: string, state: RedactionState): string {
|
|
102
|
+
let redacted = redactAwsAccessKeyIds(text, state);
|
|
103
|
+
redacted = replaceRegex(
|
|
104
|
+
redacted,
|
|
105
|
+
// STS XML responses are commonly pretty-printed, so the value sits on its own
|
|
106
|
+
// line between the tags. `[^<]` still refuses to cross into another element,
|
|
107
|
+
// while the call-wide input bound caps work without imposing a credential-size
|
|
108
|
+
// assumption. The lower bound leaves empty or whitespace-only bodies alone.
|
|
109
|
+
/(<((?:[A-Za-z_][\w.-]*:)?(?:SecretAccessKey|SessionToken))>)[^<]{8,1000000}(<\/\2>)/gi,
|
|
110
|
+
"$1[REDACTED_SECRET]$3",
|
|
111
|
+
state,
|
|
112
|
+
"aws_keys",
|
|
113
|
+
);
|
|
114
|
+
redacted = replaceRegex(
|
|
115
|
+
redacted,
|
|
116
|
+
/(^|[^A-Za-z0-9_-])(["']?(?:(?:aws[_-]?)?secret[_-]?access[_-]?key|(?:aws[_-]?)?session[_-]?token|x[_-]?amz[_-]?security[_-]?token)["']?\s*[=:]\s*)(\$?)(["'`])([^"'`\r\n]{8,})\4/gi,
|
|
117
|
+
"$1$2$3$4[REDACTED_SECRET]$4",
|
|
118
|
+
state,
|
|
119
|
+
"aws_keys",
|
|
120
|
+
);
|
|
121
|
+
return replaceRegex(
|
|
122
|
+
redacted,
|
|
123
|
+
/(^|[^A-Za-z0-9_-])(["']?(?:(?:aws[_-]?)?secret[_-]?access[_-]?key|(?:aws[_-]?)?session[_-]?token|x[_-]?amz[_-]?security[_-]?token)["']?\s*[=:]\s*)[^\s"'`,;{}[\]()&<>#]{8,}/gi,
|
|
124
|
+
"$1$2[REDACTED_SECRET]",
|
|
125
|
+
state,
|
|
126
|
+
"aws_keys",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function redactAwsJsonStrings(text: string, state: RedactionState, depth = 0): string {
|
|
131
|
+
const tokens = [...text.matchAll(/"(?:\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})|[^"\\\r\n])*"/g)].map(match => ({
|
|
132
|
+
start: match.index,
|
|
133
|
+
end: match.index + match[0].length,
|
|
134
|
+
raw: match[0],
|
|
135
|
+
}));
|
|
136
|
+
if (tokens.length > MAX_JSON_TOKENS) {
|
|
137
|
+
state.labels.add("oversized_content");
|
|
138
|
+
return "[REDACTED_OVERSIZED_CONTENT]";
|
|
139
|
+
}
|
|
140
|
+
const replacements = new Map<number, { end: number; value: string }>();
|
|
141
|
+
|
|
142
|
+
for (const [index, token] of tokens.entries()) {
|
|
143
|
+
let decoded: string;
|
|
144
|
+
try {
|
|
145
|
+
decoded = JSON.parse(token.raw) as string;
|
|
146
|
+
} catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (isAwsSecretField(decoded)) {
|
|
151
|
+
const separator = /^\s*:\s*/.exec(text.slice(token.end));
|
|
152
|
+
const valueToken = tokens[index + 1];
|
|
153
|
+
if (separator && valueToken?.start === token.end + separator[0].length) {
|
|
154
|
+
let value: string;
|
|
155
|
+
try {
|
|
156
|
+
value = JSON.parse(valueToken.raw) as string;
|
|
157
|
+
} catch {
|
|
158
|
+
value = "";
|
|
159
|
+
}
|
|
160
|
+
if (value.length >= 8)
|
|
161
|
+
replacements.set(valueToken.start, { end: valueToken.end, value: JSON.stringify("[REDACTED_SECRET]") });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const nestedRedacted = depth >= 3 ? "[REDACTED_NESTED_CONTENT]" : redactAwsJsonStrings(decoded, state, depth + 1);
|
|
166
|
+
const redacted = redactAwsLabeledValues(nestedRedacted, state);
|
|
167
|
+
if (redacted !== decoded && !replacements.has(token.start)) {
|
|
168
|
+
replacements.set(token.start, { end: token.end, value: JSON.stringify(redacted) });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (replacements.size === 0) return text;
|
|
173
|
+
if (replacements.size > MAX_JSON_REPLACEMENTS) {
|
|
174
|
+
state.labels.add("oversized_content");
|
|
175
|
+
return "[REDACTED_OVERSIZED_CONTENT]";
|
|
176
|
+
}
|
|
177
|
+
state.labels.add("aws_keys");
|
|
178
|
+
const chunks: string[] = [];
|
|
179
|
+
let cursor = 0;
|
|
180
|
+
for (const [start, replacement] of [...replacements.entries()].sort(([left], [right]) => left - right)) {
|
|
181
|
+
chunks.push(text.slice(cursor, start), replacement.value);
|
|
182
|
+
cursor = replacement.end;
|
|
183
|
+
}
|
|
184
|
+
chunks.push(text.slice(cursor));
|
|
185
|
+
return chunks.join("");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function assertSafeArtifactPath(artifactDir: string): void {
|
|
189
|
+
const state: RedactionState = { labels: new Set() };
|
|
190
|
+
redactContributionPrepText(artifactDir, path.join(artifactDir, "__gjc_path_probe__"), state);
|
|
191
|
+
if (["tokens", "aws_keys", "provider_keys", "auth_headers", "cookies"].some(label => state.labels.has(label))) {
|
|
192
|
+
throw new Error("Contribution prep artifact path contains credential-like material.");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
75
196
|
export function redactContributionPrepText(
|
|
76
197
|
text: string,
|
|
77
198
|
cwd: string,
|
|
78
199
|
state: RedactionState = { labels: new Set() },
|
|
79
200
|
): string {
|
|
80
|
-
|
|
201
|
+
if (text.length > MAX_REDACTION_INPUT_CHARS) {
|
|
202
|
+
state.labels.add("oversized_content");
|
|
203
|
+
return "[REDACTED_OVERSIZED_CONTENT]";
|
|
204
|
+
}
|
|
205
|
+
let redacted = redactAwsJsonStrings(text, state);
|
|
206
|
+
redacted = redactAwsLabeledValues(redacted, state);
|
|
81
207
|
redacted = replaceRegex(
|
|
82
208
|
redacted,
|
|
83
209
|
/\b(?:sk|pk|rk|xox[baprs])-[-_A-Za-z0-9]{12,}\b/g,
|
|
@@ -150,17 +276,73 @@ function formatMessage(message: AgentMessage): string {
|
|
|
150
276
|
return `## ${message.role}\n\n${JSON.stringify(message)}\n`;
|
|
151
277
|
}
|
|
152
278
|
|
|
153
|
-
|
|
279
|
+
interface BoundedGitOutput {
|
|
280
|
+
text: string;
|
|
281
|
+
oversized: boolean;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function readBoundedGitOutput(stream: ReadableStream<Uint8Array>, maxBytes: number): Promise<BoundedGitOutput> {
|
|
285
|
+
const reader = stream.getReader();
|
|
286
|
+
const chunks: Uint8Array[] = [];
|
|
287
|
+
let totalBytes = 0;
|
|
288
|
+
try {
|
|
289
|
+
for (;;) {
|
|
290
|
+
const { done, value } = await reader.read();
|
|
291
|
+
if (done) break;
|
|
292
|
+
totalBytes += value.byteLength;
|
|
293
|
+
if (totalBytes > maxBytes) return { text: "", oversized: true };
|
|
294
|
+
chunks.push(value);
|
|
295
|
+
}
|
|
296
|
+
} finally {
|
|
297
|
+
reader.releaseLock();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const bytes = new Uint8Array(totalBytes);
|
|
301
|
+
let offset = 0;
|
|
302
|
+
for (const chunk of chunks) {
|
|
303
|
+
bytes.set(chunk, offset);
|
|
304
|
+
offset += chunk.byteLength;
|
|
305
|
+
}
|
|
306
|
+
return { text: new TextDecoder().decode(bytes), oversized: false };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function gitOutput(
|
|
310
|
+
cwd: string,
|
|
311
|
+
args: string[],
|
|
312
|
+
state: RedactionState,
|
|
313
|
+
maxChars = MAX_GIT_OUTPUT_CHARS,
|
|
314
|
+
): Promise<string> {
|
|
154
315
|
try {
|
|
155
|
-
const
|
|
156
|
-
|
|
316
|
+
const proc = Bun.spawn(["git", ...args], { cwd, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
|
|
317
|
+
const stop = (): void => {
|
|
318
|
+
try {
|
|
319
|
+
proc.kill();
|
|
320
|
+
} catch {
|
|
321
|
+
// The process already exited.
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
try {
|
|
325
|
+
const output = await readBoundedGitOutput(proc.stdout, MAX_GIT_RAW_OUTPUT_BYTES);
|
|
326
|
+
if (output.oversized) {
|
|
327
|
+
stop();
|
|
328
|
+
await proc.exited;
|
|
329
|
+
state.labels.add("oversized_content");
|
|
330
|
+
return "[REDACTED_OVERSIZED_CONTENT]";
|
|
331
|
+
}
|
|
332
|
+
if ((await proc.exited) !== 0) return "";
|
|
333
|
+
return limitText(redactContributionPrepText(output.text.trim(), cwd, state), maxChars);
|
|
334
|
+
} catch {
|
|
335
|
+
stop();
|
|
336
|
+
await proc.exited;
|
|
337
|
+
return "";
|
|
338
|
+
}
|
|
157
339
|
} catch {
|
|
158
340
|
return "";
|
|
159
341
|
}
|
|
160
342
|
}
|
|
161
343
|
|
|
162
|
-
async function changedFiles(cwd: string): Promise<string[]> {
|
|
163
|
-
const output = await gitOutput(cwd, ["status", "--short"]);
|
|
344
|
+
async function changedFiles(cwd: string, state: RedactionState): Promise<string[]> {
|
|
345
|
+
const output = await gitOutput(cwd, ["status", "--short"], state);
|
|
164
346
|
return output
|
|
165
347
|
.split("\n")
|
|
166
348
|
.map(line => line.trim())
|
|
@@ -180,15 +362,23 @@ async function writeArtifact(
|
|
|
180
362
|
}
|
|
181
363
|
|
|
182
364
|
export function buildContributionPrepWorkerPrompt(manifestPath: string): string {
|
|
365
|
+
const manifestName = path.basename(manifestPath);
|
|
183
366
|
return [
|
|
184
367
|
"Prepare a maintainer-friendly contribution draft from the redacted context dump.",
|
|
185
|
-
"Read the manifest and
|
|
186
|
-
`Manifest: ${
|
|
368
|
+
"Read the manifest beside this worker prompt and its relative artifact file pointers. Do not assume transcript context was inlined here.",
|
|
369
|
+
`Manifest: ${manifestName}`,
|
|
187
370
|
"Produce structured markdown with: title, problem summary, reproduction/context, proposed fix or implementation plan, affected files, tests to run, and uncertainty/remaining risks.",
|
|
188
371
|
"Do not create GitHub issues, open PRs, push branches, or perform remote writes unless the user explicitly confirms that action in this fresh session.",
|
|
189
372
|
].join("\n");
|
|
190
373
|
}
|
|
191
374
|
|
|
375
|
+
function safeWorkspaceLabel(cwd: string): string {
|
|
376
|
+
const resolved = path.resolve(cwd);
|
|
377
|
+
const home = os.homedir();
|
|
378
|
+
if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) return shortenPath(resolved);
|
|
379
|
+
return path.basename(resolved) || ".";
|
|
380
|
+
}
|
|
381
|
+
|
|
192
382
|
export async function prepareContributionPrep(
|
|
193
383
|
context: ContributionPrepContext,
|
|
194
384
|
options: ContributionPrepOptions = {},
|
|
@@ -199,12 +389,14 @@ export async function prepareContributionPrep(
|
|
|
199
389
|
options.artifactRoot ?? path.join(context.cwd, ".gjc", "contribution-prep"),
|
|
200
390
|
safeTimestamp,
|
|
201
391
|
);
|
|
392
|
+
assertSafeArtifactPath(artifactDir);
|
|
202
393
|
await fs.mkdir(artifactDir, { recursive: true });
|
|
203
394
|
|
|
204
395
|
const redactions: RedactionState = { labels: new Set() };
|
|
205
396
|
const recentMessages = context.messages.slice(-MAX_TRANSCRIPT_MESSAGES);
|
|
206
397
|
const artifacts: ContributionPrepArtifact[] = [];
|
|
207
398
|
const redact = (text: string) => redactContributionPrepText(text, context.cwd, redactions);
|
|
399
|
+
const workspaceLabel = safeWorkspaceLabel(context.cwd);
|
|
208
400
|
|
|
209
401
|
artifacts.push(
|
|
210
402
|
await writeArtifact(
|
|
@@ -223,8 +415,8 @@ export async function prepareContributionPrep(
|
|
|
223
415
|
[
|
|
224
416
|
`# Contribution prep context`,
|
|
225
417
|
`Source session: ${context.sessionId}`,
|
|
226
|
-
`Session file: ${context.sessionFile
|
|
227
|
-
`Working directory: ${
|
|
418
|
+
`Session file: ${context.sessionFile ? path.basename(context.sessionFile) : "(none)"}`,
|
|
419
|
+
`Working directory: ${workspaceLabel}`,
|
|
228
420
|
options.customInstructions || context.customInstructions
|
|
229
421
|
? `Custom instructions: ${options.customInstructions ?? context.customInstructions}`
|
|
230
422
|
: "Custom instructions: (none)",
|
|
@@ -233,8 +425,8 @@ export async function prepareContributionPrep(
|
|
|
233
425
|
),
|
|
234
426
|
);
|
|
235
427
|
|
|
236
|
-
const gitHead = (await gitOutput(context.cwd, ["rev-parse", "HEAD"])) || null;
|
|
237
|
-
const files = await changedFiles(context.cwd);
|
|
428
|
+
const gitHead = (await gitOutput(context.cwd, ["rev-parse", "HEAD"], redactions)) || null;
|
|
429
|
+
const files = await changedFiles(context.cwd, redactions);
|
|
238
430
|
artifacts.push(
|
|
239
431
|
await writeArtifact(artifactDir, "changed-files.txt", "Changed files from git status", redact(files.join("\n"))),
|
|
240
432
|
);
|
|
@@ -243,7 +435,7 @@ export async function prepareContributionPrep(
|
|
|
243
435
|
artifactDir,
|
|
244
436
|
"git-diff.patch",
|
|
245
437
|
"Bounded redacted git diff",
|
|
246
|
-
|
|
438
|
+
await gitOutput(context.cwd, ["diff", "--no-ext-diff"], redactions),
|
|
247
439
|
),
|
|
248
440
|
);
|
|
249
441
|
artifacts.push(
|
|
@@ -253,7 +445,7 @@ export async function prepareContributionPrep(
|
|
|
253
445
|
"Redacted environment and reproduction metadata",
|
|
254
446
|
redact(
|
|
255
447
|
[
|
|
256
|
-
`cwd: ${
|
|
448
|
+
`cwd: ${workspaceLabel}`,
|
|
257
449
|
`git_head: ${gitHead ?? "unknown"}`,
|
|
258
450
|
`platform: ${process.platform}`,
|
|
259
451
|
`arch: ${process.arch}`,
|
|
@@ -266,15 +458,16 @@ export async function prepareContributionPrep(
|
|
|
266
458
|
const manifestPath = path.join(artifactDir, "manifest.json");
|
|
267
459
|
const workerPromptPath = path.join(artifactDir, "worker-prompt.md");
|
|
268
460
|
await Bun.write(workerPromptPath, `${buildContributionPrepWorkerPrompt(manifestPath)}\n`);
|
|
461
|
+
const manifestArtifacts = artifacts.map(artifact => ({ ...artifact, path: path.basename(artifact.path) }));
|
|
269
462
|
|
|
270
463
|
const manifest: ContributionPrepManifest = {
|
|
271
464
|
schema_version: CONTRIBUTION_PREP_SCHEMA_VERSION,
|
|
272
|
-
source_session_id: context.sessionId,
|
|
465
|
+
source_session_id: redact(context.sessionId),
|
|
273
466
|
created_at: createdAt,
|
|
274
|
-
cwd: redact(
|
|
467
|
+
cwd: redact(workspaceLabel),
|
|
275
468
|
git_head: gitHead,
|
|
276
469
|
changed_files: files.map(redact),
|
|
277
|
-
artifacts,
|
|
470
|
+
artifacts: manifestArtifacts,
|
|
278
471
|
redactions: [...redactions.labels].sort(),
|
|
279
472
|
recommended_output: [
|
|
280
473
|
"title",
|
|
@@ -285,7 +478,7 @@ export async function prepareContributionPrep(
|
|
|
285
478
|
"tests to run",
|
|
286
479
|
"uncertainty / remaining risks",
|
|
287
480
|
],
|
|
288
|
-
worker_prompt_path: workerPromptPath,
|
|
481
|
+
worker_prompt_path: path.basename(workerPromptPath),
|
|
289
482
|
};
|
|
290
483
|
await Bun.write(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
291
484
|
|
|
@@ -309,8 +502,8 @@ export async function prepareContributionPrep(
|
|
|
309
502
|
});
|
|
310
503
|
const command = resolveGjcCommand();
|
|
311
504
|
await spawn(
|
|
312
|
-
[command.cmd, ...command.args, "--no-skills", "--", `@${workerPromptPath}`],
|
|
313
|
-
|
|
505
|
+
[command.cmd, ...command.args, "--no-skills", "--", `@${path.basename(workerPromptPath)}`],
|
|
506
|
+
artifactDir,
|
|
314
507
|
command.shell,
|
|
315
508
|
);
|
|
316
509
|
spawned = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cleanReason } from "@gajae-code/ai/auth-broker/redact";
|
|
1
2
|
import type { FallbackTriggerClass } from "@gajae-code/ai/utils/fallback-transport";
|
|
2
3
|
|
|
3
4
|
/** Immutable configured fallback intent. Transient attempt state never belongs here. */
|
|
@@ -265,6 +266,43 @@ export function compactionRetryDelay(
|
|
|
265
266
|
return Math.max(0, bounded);
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
export interface CompactionCandidateFailure {
|
|
270
|
+
readonly model: string;
|
|
271
|
+
readonly message: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function sanitizeCompactionCandidateFailureMessage(message: string): string {
|
|
275
|
+
const cleaned = cleanReason(message) ?? "Compaction candidate failed.";
|
|
276
|
+
return cleaned.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s<>'"]+/gi, "[redacted URL]");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Error for an auto-compaction run whose every candidate failed.
|
|
281
|
+
*
|
|
282
|
+
* The candidate chain starts at the session model and ends on a same-provider
|
|
283
|
+
* largest-context fallback, so surfacing only the final error named a model
|
|
284
|
+
* the user never chose and hid that their own model had already failed the
|
|
285
|
+
* same way. The message leads with the first failure (the one the user can act
|
|
286
|
+
* on), then lists every candidate that was tried with its own error. `cause`
|
|
287
|
+
* is the final error so callers that classify by type keep the same object.
|
|
288
|
+
*/
|
|
289
|
+
export function describeCompactionCandidateFailures(
|
|
290
|
+
failures: readonly CompactionCandidateFailure[],
|
|
291
|
+
lastError: unknown,
|
|
292
|
+
): Error {
|
|
293
|
+
if (failures.length === 0)
|
|
294
|
+
return new Error("Compaction failed: no candidate failure details available.", { cause: lastError });
|
|
295
|
+
const first = failures[0]!;
|
|
296
|
+
const firstMessage = sanitizeCompactionCandidateFailureMessage(first.message);
|
|
297
|
+
const lines = failures.map(
|
|
298
|
+
failure => ` ${failure.model}: ${sanitizeCompactionCandidateFailureMessage(failure.message)}`,
|
|
299
|
+
);
|
|
300
|
+
return new Error(
|
|
301
|
+
`${firstMessage} (${first.model}); ${failures.length} compaction candidates failed:\n${lines.join("\n")}`,
|
|
302
|
+
{ cause: lastError },
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
268
306
|
/** Retry-After is intentionally uncapped. */
|
|
269
307
|
export function effectiveFallbackDelay(
|
|
270
308
|
baseDelayMs: number,
|
|
@@ -52,6 +52,20 @@ const LOCK_LEASE_MS = 60_000;
|
|
|
52
52
|
const LOCK_HEARTBEAT_MS = 10_000;
|
|
53
53
|
const LOCK_WAIT_MS = 5_000;
|
|
54
54
|
|
|
55
|
+
/** A pre-mutation append fence rejection: no bytes from this append were written. */
|
|
56
|
+
export class ManagedAppendIdentityMismatchError extends Error {
|
|
57
|
+
readonly code = "managed_append_identity_mismatch";
|
|
58
|
+
readonly relativePath: string;
|
|
59
|
+
readonly operatorMessage =
|
|
60
|
+
"Session was resumed by another process; this resume did not run. Retry, or resume a different session.";
|
|
61
|
+
|
|
62
|
+
constructor(relativePath: string) {
|
|
63
|
+
super("managed_append_identity_mismatch");
|
|
64
|
+
this.name = "ManagedAppendIdentityMismatchError";
|
|
65
|
+
this.relativePath = relativePath;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
export class ManagedPublishError extends Error {
|
|
56
70
|
readonly classification:
|
|
57
71
|
| "destination_conflict"
|
|
@@ -1806,7 +1820,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1806
1820
|
current.size.toString() === expected.size &&
|
|
1807
1821
|
current.sha256 === expected.sha256;
|
|
1808
1822
|
if (!identityMatches) {
|
|
1809
|
-
throw new
|
|
1823
|
+
throw new ManagedAppendIdentityMismatchError(relativePath);
|
|
1810
1824
|
}
|
|
1811
1825
|
// mtime/ctime drift on the same file object is harmless. The append
|
|
1812
1826
|
// helper performs its own descriptor-bound mutation check.
|
|
@@ -1820,7 +1834,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1820
1834
|
return managedAppendReceiptFromIdentity(appended);
|
|
1821
1835
|
}
|
|
1822
1836
|
const current = this.captureBoundedAppendExpectation(relativePath);
|
|
1823
|
-
if (!current) throw new
|
|
1837
|
+
if (!current) throw new ManagedAppendIdentityMismatchError(relativePath);
|
|
1824
1838
|
if (
|
|
1825
1839
|
current.dev !== expected.dev ||
|
|
1826
1840
|
current.ino !== expected.ino ||
|
|
@@ -1828,7 +1842,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1828
1842
|
current.size !== expected.size ||
|
|
1829
1843
|
current.sha256 !== expected.sha256
|
|
1830
1844
|
)
|
|
1831
|
-
throw new
|
|
1845
|
+
throw new ManagedAppendIdentityMismatchError(relativePath);
|
|
1832
1846
|
const descriptorMatches =
|
|
1833
1847
|
current.dev === expected.dev &&
|
|
1834
1848
|
current.ino === expected.ino &&
|
|
@@ -1862,7 +1876,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1862
1876
|
expected: ManagedFileIdentity,
|
|
1863
1877
|
): ManagedAppendReceipt {
|
|
1864
1878
|
const bounded = this.captureBoundedAppendExpectation(relativePath);
|
|
1865
|
-
if (!bounded) throw new
|
|
1879
|
+
if (!bounded) throw new ManagedAppendIdentityMismatchError(relativePath);
|
|
1866
1880
|
const descriptorMatches =
|
|
1867
1881
|
bounded.dev === expected.dev.toString() &&
|
|
1868
1882
|
bounded.ino === expected.ino.toString() &&
|
|
@@ -1882,7 +1896,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1882
1896
|
bounded.size !== String(expected.size) ||
|
|
1883
1897
|
bounded.sha256 !== expected.sha256
|
|
1884
1898
|
)
|
|
1885
|
-
throw new
|
|
1899
|
+
throw new ManagedAppendIdentityMismatchError(relativePath);
|
|
1886
1900
|
}
|
|
1887
1901
|
return this.appendExpectedSync(relativePath, bytes, bounded);
|
|
1888
1902
|
}
|
package/src/session/messages.ts
CHANGED
|
@@ -212,6 +212,7 @@ export interface BashExecutionMessage {
|
|
|
212
212
|
command: string;
|
|
213
213
|
output: string;
|
|
214
214
|
exitCode: number | undefined;
|
|
215
|
+
signal?: string;
|
|
215
216
|
cancelled: boolean;
|
|
216
217
|
truncated: boolean;
|
|
217
218
|
meta?: OutputMeta;
|
|
@@ -313,6 +314,10 @@ export function bashExecutionToText(msg: BashExecutionMessage): string {
|
|
|
313
314
|
}
|
|
314
315
|
if (msg.cancelled) {
|
|
315
316
|
text += "\n\n(command cancelled)";
|
|
317
|
+
} else if (msg.signal) {
|
|
318
|
+
text += `\n\nCommand terminated by ${msg.signal}${
|
|
319
|
+
msg.exitCode !== null && msg.exitCode !== undefined ? ` (exit code ${msg.exitCode})` : ""
|
|
320
|
+
}`;
|
|
316
321
|
} else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {
|
|
317
322
|
text += `\n\nCommand exited with code ${msg.exitCode}`;
|
|
318
323
|
}
|
|
@@ -112,6 +112,7 @@ import {
|
|
|
112
112
|
captureManagedFileNoFollow,
|
|
113
113
|
fsyncManagedArtifactTree,
|
|
114
114
|
MANAGED_ARTIFACT_MAX_FILE_BYTES,
|
|
115
|
+
ManagedAppendIdentityMismatchError,
|
|
115
116
|
type ManagedAppendReceipt,
|
|
116
117
|
type ManagedBoundedAppendExpectation,
|
|
117
118
|
ManagedCommittedMutationError,
|
|
@@ -11873,6 +11874,10 @@ export class SessionManager {
|
|
|
11873
11874
|
return false;
|
|
11874
11875
|
}
|
|
11875
11876
|
|
|
11877
|
+
/** Ownership loss remains observable even when an agent event listener aborts the turn. */
|
|
11878
|
+
getAppendIdentityMismatch(): ManagedAppendIdentityMismatchError | undefined {
|
|
11879
|
+
return this.#persistError instanceof ManagedAppendIdentityMismatchError ? this.#persistError : undefined;
|
|
11880
|
+
}
|
|
11876
11881
|
#recordPersistError(err: unknown): Error {
|
|
11877
11882
|
const normalized = toError(err);
|
|
11878
11883
|
if (!this.#persistError) this.#persistError = normalized;
|
|
@@ -16255,7 +16260,11 @@ export class SessionManager {
|
|
|
16255
16260
|
}
|
|
16256
16261
|
}
|
|
16257
16262
|
const outcome = await this.closeStrict();
|
|
16258
|
-
return priorFlushError &&
|
|
16263
|
+
return priorFlushError &&
|
|
16264
|
+
!(priorFlushError instanceof ManagedAppendIdentityMismatchError) &&
|
|
16265
|
+
outcome.kind === "closed"
|
|
16266
|
+
? { kind: "close_unknown", error: priorFlushError }
|
|
16267
|
+
: outcome;
|
|
16259
16268
|
}
|
|
16260
16269
|
|
|
16261
16270
|
/**
|
|
@@ -16280,6 +16289,21 @@ export class SessionManager {
|
|
|
16280
16289
|
});
|
|
16281
16290
|
}
|
|
16282
16291
|
if (preparedCleanupError) return { kind: "close_failed_retryable", error: preparedCleanupError };
|
|
16292
|
+
await this.#persistChain;
|
|
16293
|
+
// Managed appends are synchronous and reject identity drift before writing.
|
|
16294
|
+
// Disown the stale transcript, not an uncertain OS writer: never rewrite a
|
|
16295
|
+
// successor owned by another process while disposing this rejected resume.
|
|
16296
|
+
if (
|
|
16297
|
+
this.destination.kind === "managed" &&
|
|
16298
|
+
!this.#persistWriter &&
|
|
16299
|
+
this.#persistError instanceof ManagedAppendIdentityMismatchError
|
|
16300
|
+
) {
|
|
16301
|
+
this.#needsFullRewriteOnNextPersist = false;
|
|
16302
|
+
this.#strictResumeMutationPending = false;
|
|
16303
|
+
this.#managedPersistExpectedIdentity = undefined;
|
|
16304
|
+
this.#persistError = undefined;
|
|
16305
|
+
this.#persistErrorReported = false;
|
|
16306
|
+
}
|
|
16283
16307
|
let priorPersistError = this.#persistError;
|
|
16284
16308
|
if (this.#needsFullRewriteOnNextPersist && (!this.#readOnlyResume || this.#strictResumeMutationPending)) {
|
|
16285
16309
|
await this.#persistChain.catch(() => {});
|
|
@@ -17379,10 +17403,7 @@ export class SessionManager {
|
|
|
17379
17403
|
receipt = store.appendExpectedIdentitySync(relativePath, bytes, this.#managedPersistExpectedIdentity);
|
|
17380
17404
|
} catch (err) {
|
|
17381
17405
|
const predecessorMissing = store.descriptorExpected(relativePath) === null;
|
|
17382
|
-
if (
|
|
17383
|
-
!isEnoent(err) &&
|
|
17384
|
-
(!(err instanceof Error) || err.message !== "managed_append_identity_mismatch" || !predecessorMissing)
|
|
17385
|
-
)
|
|
17406
|
+
if (!isEnoent(err) && (!(err instanceof ManagedAppendIdentityMismatchError) || !predecessorMissing))
|
|
17386
17407
|
throw err;
|
|
17387
17408
|
// Appending only the new records would create a truncated transcript.
|
|
17388
17409
|
// Recreate the missing file from the complete resident entry set instead.
|
|
@@ -17724,6 +17745,9 @@ export class SessionManager {
|
|
|
17724
17745
|
if (entry.type === "label") this.#labelRevision++;
|
|
17725
17746
|
if (sidecarAppendCharge > 0) activeRuntime?.accountant.release(sidecarAppendCharge);
|
|
17726
17747
|
if (sidecarTailCharge > 0) activeRuntime?.tailCache.release(sidecarTailCharge);
|
|
17748
|
+
// The pre-write fence is an expected ownership loss, not an uncertain
|
|
17749
|
+
// append. Preserve its type after rolling back the rejected entry.
|
|
17750
|
+
if (error instanceof ManagedAppendIdentityMismatchError) throw error;
|
|
17727
17751
|
throw new SessionAppendPersistenceError(
|
|
17728
17752
|
priorPersistenceError ? "prior_failure" : "current_append",
|
|
17729
17753
|
residentEntry.id,
|
|
@@ -59,7 +59,11 @@ const REDACTION_RULES: readonly RedactionRule[] = [
|
|
|
59
59
|
// Long hex secrets (64+ hex chars: webhook secrets, signing keys).
|
|
60
60
|
{ id: "hex-secret", pattern: new RegExp(`\\b${HEX}{64,}\\b`, "g") },
|
|
61
61
|
// Basic-auth credentials embedded in URLs (https://user:pass@host).
|
|
62
|
-
|
|
62
|
+
// The scheme repetition is bounded. Unbounded `[a-z0-9+.-]*` in front of the
|
|
63
|
+
// literal `://` re-tries every prefix of a long alphabetic run before failing,
|
|
64
|
+
// which is quadratic in the input length: 200 KB of ordinary prose costs ~10s.
|
|
65
|
+
// IANA's longest registered scheme is well under 16 characters.
|
|
66
|
+
{ id: "url-credential", pattern: /([a-z][a-z0-9+.-]{0,15}:\/\/)[^/\s:@]{1,256}:[^/\s@]{1,256}@/gi },
|
|
63
67
|
// Sensitive env/KEY assignments: OPENAI_API_KEY=..., token: ..., password = ...
|
|
64
68
|
// The name prefix is optional so a bare sensitive name (`password: …`) also matches.
|
|
65
69
|
{
|