@gajae-code/coding-agent 0.18.12 → 0.19.0
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 +467 -0
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +7 -2
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +8 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +23 -1
- package/dist/types/config/config-hot-reload.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +44 -2
- package/dist/types/config/model-profile-activation.d.ts +19 -5
- package/dist/types/config/model-registry.d.ts +16 -3
- package/dist/types/config/model-resolver.d.ts +5 -4
- package/dist/types/config/settings-schema.d.ts +11 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +9 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +1 -1
- package/dist/types/migrate/adapters/index.d.ts +35 -3
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +69 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +22 -1
- package/dist/types/sdk/broker/ensure.d.ts +5 -2
- package/dist/types/sdk/broker/hop.d.ts +9 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/session-index.d.ts +2 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/index.d.ts +21 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +8 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/session/agent-session.d.ts +50 -8
- package/dist/types/session/session-manager.d.ts +16 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +13 -2
- package/dist/types/tools/fetch.d.ts +6 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/package.json +9 -8
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/generate-sdk-operation-inventory.ts +16 -0
- package/src/capability/fs.ts +31 -3
- package/src/capability/rule.ts +11 -2
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/fast-help.ts +0 -2
- package/src/cli/migrate-cli.ts +14 -3
- package/src/cli/public-command-errors.ts +2 -0
- package/src/cli/root-flags.ts +0 -2
- package/src/cli/update-cli.ts +247 -6
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +81 -11
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +20 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload.ts +8 -0
- package/src/config/file-lock.ts +1431 -304
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-profile-activation.ts +177 -39
- package/src/config/model-registry.ts +380 -55
- package/src/config/model-resolver.ts +49 -21
- package/src/config/settings-schema.ts +13 -1
- package/src/config/settings.ts +2 -0
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +3 -1
- package/src/customization/import.ts +276 -7
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +87 -5
- package/src/debug/report-bundle.ts +71 -2
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +24 -14
- package/src/edit/index.ts +191 -5
- package/src/edit/modes/patch.ts +27 -0
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +60 -20
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/extensions/loader.ts +2 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +4 -1
- package/src/gjc-runtime/launch-tmux.ts +23 -13
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +3 -1
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +14 -14
- package/src/internal-urls/local-protocol.ts +28 -3
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +517 -2
- package/src/migrate/adapters/claude-code.ts +141 -3
- package/src/migrate/adapters/codex.ts +57 -5
- package/src/migrate/adapters/index.ts +240 -5
- package/src/migrate/adapters/opencode.ts +4 -0
- package/src/migrate/executor.ts +141 -3
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +45 -1
- package/src/migrate/types.ts +73 -3
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +271 -16
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +49 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +14 -1
- package/src/modes/interactive-mode.ts +55 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/runtime-mcp/manager.ts +8 -3
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/tool-bridge.ts +68 -4
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +2 -0
- package/src/sdk/broker/broker.ts +143 -4
- package/src/sdk/broker/ensure.ts +74 -6
- package/src/sdk/broker/hop.ts +13 -1
- package/src/sdk/broker/lifecycle-ledger.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/session-index.ts +29 -4
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +408 -14
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1537 -24
- package/src/sdk/host/session-runtime.ts +332 -36
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +14 -1
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +28 -1
- package/src/sdk/protocol/operation-inventory.generated.json +160 -0
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/session.ts +123 -36
- package/src/session/agent-session.ts +1614 -329
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +47 -18
- package/src/session/internal/managed-session-storage.ts +71 -1
- package/src/session/session-manager.ts +90 -5
- package/src/session-import/redact.ts +53 -2
- package/src/skill-state/workflow-mutation-guard.ts +1498 -18
- package/src/slash-commands/builtin-registry.ts +6 -3
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +12 -8
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/auto-generated-guard.ts +12 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +61 -1
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +99 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +9 -2
- package/src/tools/descriptors.test.ts +4 -4
- package/src/tools/descriptors.ts +16 -4
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +90 -2
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +51 -3
- package/src/tools/read.ts +9 -2
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent.ts +10 -4
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +7 -0
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +3 -2
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +46 -6
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +65 -20
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
|
@@ -293,11 +293,131 @@ export function sessionPaths(root: string, sessionId: string): SessionPaths {
|
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
const NO_FOLLOW = process.platform === "win32" ? 0 : (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
297
|
+
|
|
298
|
+
function errnoCode(error: unknown): string | undefined {
|
|
299
|
+
return error instanceof Error && "code" in error ? (error as NodeJS.ErrnoException).code : undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function sameInode(left: fsSync.BigIntStats, right: fsSync.BigIntStats): boolean {
|
|
303
|
+
return left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Leaf directory mode 0700. `lstat` plus `chmod` does not need read permission, so a
|
|
308
|
+
* mode 0300 directory stays writable. A symlink leaf is refused before chmod.
|
|
309
|
+
* This does not pin the directory against a later rename in a writable parent.
|
|
310
|
+
*/
|
|
311
|
+
async function ensureOwnerDirectory(dir: string): Promise<void> {
|
|
312
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
313
|
+
let current: fsSync.Stats;
|
|
314
|
+
try {
|
|
315
|
+
current = await fs.lstat(dir);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const code = errnoCode(error);
|
|
318
|
+
if (code === "ELOOP" || code === "ENOTDIR") throw new Error("harness state directory is not a directory");
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
if (current.isSymbolicLink() || !current.isDirectory())
|
|
322
|
+
throw new Error("harness state directory is not a directory");
|
|
323
|
+
if (process.platform === "win32" || (current.mode & 0o077) === 0) return;
|
|
324
|
+
await fs.chmod(dir, 0o700);
|
|
325
|
+
const tightened = await fs.lstat(dir);
|
|
326
|
+
if (tightened.isSymbolicLink() || !tightened.isDirectory() || (tightened.mode & 0o777) !== 0o700) {
|
|
327
|
+
throw new Error("harness state directory could not be tightened to mode 0700");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function assertRegularOrMissing(file: string): Promise<void> {
|
|
332
|
+
try {
|
|
333
|
+
const existing = await fs.lstat(file);
|
|
334
|
+
if (existing.isSymbolicLink() || !existing.isFile()) throw new Error("harness state path is not a regular file");
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (errnoCode(error) !== "ENOENT") throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Append like the workflow owner-only writer: mode 0600, `O_NOFOLLOW` when the file
|
|
342
|
+
* exists, `O_EXCL` when it does not, and chmod to 0600 when group or other bits are set.
|
|
343
|
+
*/
|
|
344
|
+
async function appendOwnerOnly(filePath: string, text: string): Promise<void> {
|
|
345
|
+
await ensureOwnerDirectory(path.dirname(filePath));
|
|
346
|
+
let handle: fs.FileHandle | undefined;
|
|
347
|
+
let initial: fsSync.BigIntStats | undefined;
|
|
348
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
349
|
+
initial = undefined;
|
|
350
|
+
try {
|
|
351
|
+
initial = await fs.lstat(filePath, { bigint: true });
|
|
352
|
+
if (initial.isSymbolicLink() || !initial.isFile()) throw new Error("harness state path is not a regular file");
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (errnoCode(error) !== "ENOENT") throw error;
|
|
355
|
+
}
|
|
356
|
+
const flags = initial
|
|
357
|
+
? fsSync.constants.O_WRONLY | fsSync.constants.O_APPEND | NO_FOLLOW
|
|
358
|
+
: fsSync.constants.O_WRONLY | fsSync.constants.O_APPEND | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL;
|
|
359
|
+
try {
|
|
360
|
+
handle = await fs.open(filePath, flags, 0o600);
|
|
361
|
+
break;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
const code = errnoCode(error);
|
|
364
|
+
if (code === "ELOOP") throw new Error("harness state path is not a regular file");
|
|
365
|
+
if (initial === undefined && code === "EEXIST" && attempt === 0) continue;
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (!handle) throw new Error("harness state path identity changed before append");
|
|
370
|
+
try {
|
|
371
|
+
const opened = await handle.stat({ bigint: true });
|
|
372
|
+
const named = await fs.lstat(filePath, { bigint: true });
|
|
373
|
+
if (
|
|
374
|
+
opened.isSymbolicLink() ||
|
|
375
|
+
!opened.isFile() ||
|
|
376
|
+
named.isSymbolicLink() ||
|
|
377
|
+
!named.isFile() ||
|
|
378
|
+
(initial !== undefined && !sameInode(initial, opened)) ||
|
|
379
|
+
!sameInode(opened, named)
|
|
380
|
+
) {
|
|
381
|
+
throw new Error("harness state path identity changed before append");
|
|
382
|
+
}
|
|
383
|
+
if (process.platform !== "win32" && (Number(opened.mode) & 0o077) !== 0) {
|
|
384
|
+
await handle.chmod(0o600);
|
|
385
|
+
const tightened = await handle.stat({ bigint: true });
|
|
386
|
+
if ((Number(tightened.mode) & 0o777) !== 0o600) {
|
|
387
|
+
throw new Error("harness state path could not be tightened to mode 0600");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await handle.writeFile(text, "utf8");
|
|
391
|
+
} finally {
|
|
392
|
+
await handle.close();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
296
396
|
async function writeJsonAtomic(file: string, value: unknown): Promise<void> {
|
|
297
|
-
await
|
|
397
|
+
await ensureOwnerDirectory(path.dirname(file));
|
|
398
|
+
await assertRegularOrMissing(file);
|
|
298
399
|
const tmp = `${file}.tmp-${randomBytes(4).toString("hex")}`;
|
|
299
|
-
|
|
300
|
-
await fs.
|
|
400
|
+
const flags = fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL | NO_FOLLOW;
|
|
401
|
+
const handle = await fs.open(tmp, flags, 0o600);
|
|
402
|
+
try {
|
|
403
|
+
if (process.platform !== "win32") {
|
|
404
|
+
await handle.chmod(0o600);
|
|
405
|
+
const created = await handle.stat();
|
|
406
|
+
if (!created.isFile() || (created.mode & 0o777) !== 0o600) {
|
|
407
|
+
throw new Error("harness state path could not be tightened to mode 0600");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
411
|
+
} finally {
|
|
412
|
+
await handle.close();
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
await assertRegularOrMissing(file);
|
|
416
|
+
await fs.rename(tmp, file);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
await fs.rm(tmp, { force: true }).catch(() => undefined);
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
301
421
|
}
|
|
302
422
|
|
|
303
423
|
async function readJson<T>(file: string): Promise<T | null> {
|
|
@@ -396,7 +516,6 @@ export async function resolveHarnessSessionRoot(
|
|
|
396
516
|
|
|
397
517
|
export async function writeSessionState(root: string, state: SessionState): Promise<void> {
|
|
398
518
|
const paths = sessionPaths(root, state.sessionId);
|
|
399
|
-
await fs.mkdir(paths.dir, { recursive: true });
|
|
400
519
|
await writeJsonAtomic(paths.state, state);
|
|
401
520
|
}
|
|
402
521
|
|
|
@@ -407,8 +526,7 @@ export async function sessionExists(root: string, sessionId: string): Promise<bo
|
|
|
407
526
|
/** Append a single severity envelope to events.jsonl. Single-writer discipline is the owner's job (M3). */
|
|
408
527
|
export async function appendEvent(root: string, sessionId: string, envelope: EventEnvelope): Promise<void> {
|
|
409
528
|
const paths = sessionPaths(root, sessionId);
|
|
410
|
-
await
|
|
411
|
-
await fs.appendFile(paths.events, `${JSON.stringify(envelope)}\n`, "utf8");
|
|
529
|
+
await appendOwnerOnly(paths.events, `${JSON.stringify(envelope)}\n`);
|
|
412
530
|
}
|
|
413
531
|
|
|
414
532
|
/** Read events from cursor (exclusive). Tail-only: never mutates the log. */
|
package/src/hindsight/content.ts
CHANGED
|
@@ -20,27 +20,126 @@ export interface RecallResultLike {
|
|
|
20
20
|
mentioned_at?: string | null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const MEMORY_TAG_NAMES = ["memories", "hindsight_memories", "relevant_memories", "mental_models"] as const;
|
|
24
|
+
|
|
25
|
+
function memoryTagExpression(name: string): RegExp {
|
|
26
|
+
return new RegExp(`<\\s*\\/?\\s*${name}\\s*>`, "gi");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface MemoryTagSpan {
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
close: boolean;
|
|
33
|
+
paired: boolean;
|
|
34
|
+
absorbed: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findMemoryTagSpans(content: string, name: string): MemoryTagSpan[] {
|
|
38
|
+
const spans: MemoryTagSpan[] = [];
|
|
39
|
+
for (const match of content.matchAll(memoryTagExpression(name))) {
|
|
40
|
+
const raw = match[0];
|
|
41
|
+
const start = match.index ?? 0;
|
|
42
|
+
spans.push({
|
|
43
|
+
start,
|
|
44
|
+
end: start + raw.length,
|
|
45
|
+
close: raw.includes("/"),
|
|
46
|
+
paired: false,
|
|
47
|
+
absorbed: false,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return spans;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mergeSpans(ranges: Array<[number, number]>): Array<[number, number]> {
|
|
54
|
+
const sorted = ranges.filter(([start, end]) => end > start).sort((a, b) => a[0] - b[0] || b[1] - a[1]);
|
|
55
|
+
const merged: Array<[number, number]> = [];
|
|
56
|
+
for (const [start, end] of sorted) {
|
|
57
|
+
const last = merged[merged.length - 1];
|
|
58
|
+
if (!last || start > last[1]) merged.push([start, end]);
|
|
59
|
+
else last[1] = Math.max(last[1], end);
|
|
60
|
+
}
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Remove one memory-tag family.
|
|
66
|
+
*
|
|
67
|
+
* A matched block is removed whole. An extra closer after that block is the
|
|
68
|
+
* recall frame ending early: the escaped text through that closer is removed
|
|
69
|
+
* so it is not retained as instructions. A closer or opener with no block
|
|
70
|
+
* before it is removed as a token only, and the surrounding prose stays.
|
|
71
|
+
*/
|
|
72
|
+
function stripMemoryTagFamily(content: string, name: string): string {
|
|
73
|
+
const tags = findMemoryTagSpans(content, name);
|
|
74
|
+
if (tags.length === 0) return content;
|
|
75
|
+
|
|
76
|
+
const ranges: Array<[number, number]> = [];
|
|
77
|
+
const stack: MemoryTagSpan[] = [];
|
|
78
|
+
for (const tag of tags) {
|
|
79
|
+
if (!tag.close) {
|
|
80
|
+
stack.push(tag);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const open = stack.pop();
|
|
84
|
+
if (!open) continue;
|
|
85
|
+
open.paired = true;
|
|
86
|
+
tag.paired = true;
|
|
87
|
+
ranges.push([open.start, tag.end]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const tag of tags) {
|
|
91
|
+
if (!tag.close || tag.paired) continue;
|
|
92
|
+
let previous: MemoryTagSpan | undefined;
|
|
93
|
+
for (const candidate of tags) {
|
|
94
|
+
if (candidate.end <= tag.start) previous = candidate;
|
|
95
|
+
else break;
|
|
96
|
+
}
|
|
97
|
+
const followsClosedBlock = Boolean(previous?.close && (previous.paired || previous.absorbed));
|
|
98
|
+
if (followsClosedBlock && previous) {
|
|
99
|
+
ranges.push([previous.end, tag.end]);
|
|
100
|
+
tag.absorbed = true;
|
|
101
|
+
} else {
|
|
102
|
+
ranges.push([tag.start, tag.end]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const tag of tags) {
|
|
107
|
+
if (!tag.close && !tag.paired) ranges.push([tag.start, tag.end]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let out = content;
|
|
111
|
+
for (const [start, end] of mergeSpans(ranges).reverse()) {
|
|
112
|
+
out = out.slice(0, start) + out.slice(end);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Escape recognized tag delimiters so recalled text cannot close the outer frame. */
|
|
118
|
+
function stripMemoryTagTokens(text: string): string {
|
|
119
|
+
let out = text;
|
|
120
|
+
for (const name of MEMORY_TAG_NAMES) {
|
|
121
|
+
out = out.replace(memoryTagExpression(name), tag => tag.replace("<", "<"));
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
27
125
|
|
|
28
126
|
/**
|
|
29
127
|
* Strip `<memories>`, `<mental_models>`, and legacy memory blocks.
|
|
30
128
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* retaining.
|
|
129
|
+
* Matching is case-insensitive and allows whitespace inside the tag.
|
|
130
|
+
* Both per-turn recall and curated mental models are injected around the
|
|
131
|
+
* prompt. If a closer, a case variant, or an early-ended frame leaks into
|
|
132
|
+
* the retention transcript, the next retain stores the breakout as a new
|
|
133
|
+
* memory. Always strip before retaining.
|
|
37
134
|
*/
|
|
38
135
|
export function stripMemoryTags(content: string): string {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
136
|
+
let out = content;
|
|
137
|
+
while (true) {
|
|
138
|
+
let stripped = out;
|
|
139
|
+
for (const name of MEMORY_TAG_NAMES) stripped = stripMemoryTagFamily(stripped, name);
|
|
140
|
+
if (stripped === out) return out;
|
|
141
|
+
out = stripped;
|
|
142
|
+
}
|
|
44
143
|
}
|
|
45
144
|
|
|
46
145
|
/** Format recall results into a bullet list for context injection. */
|
|
@@ -50,7 +149,8 @@ export function formatMemories(results: RecallResultLike[]): string {
|
|
|
50
149
|
.map(r => {
|
|
51
150
|
const typeStr = r.type ? ` [${r.type}]` : "";
|
|
52
151
|
const dateStr = r.mentioned_at ? ` (${r.mentioned_at})` : "";
|
|
53
|
-
|
|
152
|
+
const text = stripMemoryTagTokens(r.text);
|
|
153
|
+
return `- ${text}${typeStr}${dateStr}`;
|
|
54
154
|
})
|
|
55
155
|
.join("\n\n");
|
|
56
156
|
}
|
|
@@ -219,6 +219,17 @@ const TRUNCATION_MARKER = "\n\n…[mental-model snapshot truncated at render bud
|
|
|
219
219
|
*/
|
|
220
220
|
const MIN_CONTENT_ROOM_CHARS = 64;
|
|
221
221
|
|
|
222
|
+
const MEMORY_FRAME_NAMES = ["memories", "hindsight_memories", "relevant_memories", "mental_models"] as const;
|
|
223
|
+
|
|
224
|
+
/** Escape the same frame tokens as recalled memory text so a stored closer cannot end this block early. */
|
|
225
|
+
function escapeMemoryFrameTokens(text: string): string {
|
|
226
|
+
let out = text;
|
|
227
|
+
for (const name of MEMORY_FRAME_NAMES) {
|
|
228
|
+
out = out.replace(new RegExp(`<\\s*\\/?\\s*${name}\\s*>`, "gi"), tag => tag.replace("<", "<"));
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
|
|
222
233
|
/** Smallest budget that can yield a usable block (wrapper + preamble + marker + a few chars of content). */
|
|
223
234
|
function minRenderBudgetChars(): number {
|
|
224
235
|
const cleanOverhead = `<mental_models>\n${PREAMBLE}\n\n\n</mental_models>`.length;
|
|
@@ -235,7 +246,6 @@ export function renderMentalModelsBlock(models: MentalModelSummary[], budgetChar
|
|
|
235
246
|
if (budgetChars < minRenderBudgetChars()) return "";
|
|
236
247
|
|
|
237
248
|
const truncatedOverhead = `<mental_models>\n${PREAMBLE}\n\n${TRUNCATION_MARKER}\n</mental_models>`.length;
|
|
238
|
-
const cleanOverhead = `<mental_models>\n${PREAMBLE}\n\n\n</mental_models>`.length;
|
|
239
249
|
const innerBudget = Math.max(0, budgetChars - truncatedOverhead);
|
|
240
250
|
const perModelBudget = Math.max(120, Math.floor(innerBudget / Math.max(1, models.length)));
|
|
241
251
|
|
|
@@ -260,18 +270,20 @@ export function renderMentalModelsBlock(models: MentalModelSummary[], budgetChar
|
|
|
260
270
|
consumed += sectionCost;
|
|
261
271
|
}
|
|
262
272
|
|
|
273
|
+
// Escape after the heading/body join. A closer split across that newline
|
|
274
|
+
// (`</mental_models` + `>`) is not visible in either field alone.
|
|
275
|
+
// The wrapper is added after this step and is not escaped.
|
|
276
|
+
let inner = escapeMemoryFrameTokens(sections.join("\n\n"));
|
|
263
277
|
const tail = truncated ? TRUNCATION_MARKER : "";
|
|
264
|
-
let assembled = `<mental_models>\n${PREAMBLE}\n\n${
|
|
278
|
+
let assembled = `<mental_models>\n${PREAMBLE}\n\n${inner}${tail}\n</mental_models>`;
|
|
265
279
|
|
|
266
|
-
// Final hard-cap:
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
// still find the closing tag.
|
|
280
|
+
// Final hard-cap: slice the already escaped body so a cut cannot restore a raw closer.
|
|
281
|
+
// The marker is always appended on this path, so reserve its overhead.
|
|
282
|
+
// Escaped tags are longer than the raw text the per-model budget measured.
|
|
270
283
|
if (assembled.length > budgetChars) {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
assembled = `<mental_models>\n${PREAMBLE}\n\n${body}${TRUNCATION_MARKER}\n</mental_models>`;
|
|
284
|
+
const room = Math.max(0, budgetChars - truncatedOverhead);
|
|
285
|
+
inner = inner.slice(0, room).trimEnd();
|
|
286
|
+
assembled = `<mental_models>\n${PREAMBLE}\n\n${inner}${TRUNCATION_MARKER}\n</mental_models>`;
|
|
275
287
|
}
|
|
276
288
|
return assembled;
|
|
277
289
|
}
|
package/src/hindsight/state.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { logger } from "@gajae-code/utils";
|
|
4
|
+
import { redactMemorySecretsForTesting as redactMemorySecrets } from "../memories";
|
|
4
5
|
import type { AgentSession } from "../session/agent-session";
|
|
5
6
|
import { type BankScope, ensureBankMission } from "./bank";
|
|
6
7
|
import type { HindsightApi, MemoryItemInput } from "./client";
|
|
@@ -149,8 +150,8 @@ export class HindsightRetainQueue {
|
|
|
149
150
|
try {
|
|
150
151
|
await ensureBankMission(state.client, state.bankId, state.config, state.missionsSet);
|
|
151
152
|
const batch: MemoryItemInput[] = items.map(item => ({
|
|
152
|
-
content: item.content,
|
|
153
|
-
context: item.context
|
|
153
|
+
content: redactMemorySecrets(item.content),
|
|
154
|
+
context: item.context === undefined ? state.config.retainContext : redactMemorySecrets(item.context),
|
|
154
155
|
metadata: { session_id: sessionId },
|
|
155
156
|
tags: state.retainTags,
|
|
156
157
|
}));
|
|
@@ -316,8 +317,10 @@ export class HindsightSessionState {
|
|
|
316
317
|
documentId = `${this.sessionId}-${Date.now()}`;
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
const
|
|
320
|
-
if (!transcript) return false;
|
|
320
|
+
const prepared = prepareRetentionTranscript(target, true);
|
|
321
|
+
if (!prepared.transcript) return false;
|
|
322
|
+
// Same scrub the local memory backend applies before it stores consolidation text.
|
|
323
|
+
const transcript = redactMemorySecrets(prepared.transcript);
|
|
321
324
|
|
|
322
325
|
await ensureBankMission(this.client, this.bankId, this.config, this.missionsSet);
|
|
323
326
|
await this.client.retain(this.bankId, transcript, {
|
|
@@ -427,7 +427,9 @@ async function readStdinJson(): Promise<{ payload: HookPayload; parseError: Erro
|
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
async function logHookError(cwd: string, type: string, error: unknown): Promise<void> {
|
|
430
|
-
|
|
430
|
+
// Bun has already copied the project dotenv into the environment. A value
|
|
431
|
+
// that matches that snapshot must not select another session's log directory.
|
|
432
|
+
const gjcSessionId = trustedCoordinatorEnv("GJC_SESSION_ID", process.env, projectEnvSnapshot(cwd));
|
|
431
433
|
if (!gjcSessionId) {
|
|
432
434
|
console.error(
|
|
433
435
|
JSON.stringify({
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Protocol handler for artifact:// URLs.
|
|
3
3
|
*
|
|
4
4
|
* Resolves artifact IDs only against artifacts directories explicitly authorized
|
|
5
|
-
* by the caller's ResolveContext.
|
|
5
|
+
* by the caller's ResolveContext. SSH output artifacts are marked untrusted so
|
|
6
|
+
* ReadTool wraps the selected text after applying selectors and truncation.
|
|
6
7
|
*
|
|
7
8
|
* URL form:
|
|
8
9
|
* - artifact://<id> - Full artifact content
|
|
@@ -97,6 +98,7 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
97
98
|
contentType: "text/plain",
|
|
98
99
|
size: Buffer.byteLength(content, "utf-8"),
|
|
99
100
|
sourcePath: foundPath,
|
|
101
|
+
untrusted: path.basename(foundPath).endsWith(".ssh.log"),
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
}
|