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
|
@@ -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, readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -11,57 +11,53 @@ function wait(ms) {
|
|
|
11
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
}
|
|
13
13
|
describe("MemoryStorage debounced persistence", () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const storage = await MemoryStorage.create(dbPath);
|
|
19
|
-
const initialStat = readFileSync(dbPath);
|
|
20
|
-
const initialMtime = initialStat.length;
|
|
21
|
-
storage.upsertThreads([
|
|
22
|
-
{ threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
|
|
23
|
-
]);
|
|
24
|
-
storage.upsertThreads([
|
|
25
|
-
{ threadId: "t2", filePath: "/b.txt", fileSize: 200, fileMtime: 2000, cwd: "/proj" },
|
|
26
|
-
]);
|
|
27
|
-
storage.upsertThreads([
|
|
28
|
-
{ threadId: "t3", filePath: "/c.txt", fileSize: 300, fileMtime: 3000, cwd: "/proj" },
|
|
29
|
-
]);
|
|
30
|
-
const afterMutationsBuf = readFileSync(dbPath);
|
|
31
|
-
assert.deepEqual(afterMutationsBuf, initialStat, "File should not have been written yet (debounce window has not elapsed)");
|
|
32
|
-
await wait(700);
|
|
33
|
-
const afterDebounceBuf = readFileSync(dbPath);
|
|
34
|
-
assert.notDeepEqual(afterDebounceBuf, initialStat, "File should have been written after debounce window elapsed");
|
|
35
|
-
const stats = storage.getStats();
|
|
36
|
-
assert.equal(stats.totalThreads, 3);
|
|
37
|
-
storage.close();
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
14
|
+
let dir;
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (dir) {
|
|
40
17
|
rmSync(dir, { recursive: true, force: true });
|
|
41
18
|
}
|
|
42
19
|
});
|
|
20
|
+
it("multiple rapid mutations only trigger one persist write", async () => {
|
|
21
|
+
dir = makeTmpDir();
|
|
22
|
+
const dbPath = join(dir, "test.db");
|
|
23
|
+
const storage = await MemoryStorage.create(dbPath);
|
|
24
|
+
const initialStat = readFileSync(dbPath);
|
|
25
|
+
const initialMtime = initialStat.length;
|
|
26
|
+
storage.upsertThreads([
|
|
27
|
+
{ threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
|
|
28
|
+
]);
|
|
29
|
+
storage.upsertThreads([
|
|
30
|
+
{ threadId: "t2", filePath: "/b.txt", fileSize: 200, fileMtime: 2000, cwd: "/proj" },
|
|
31
|
+
]);
|
|
32
|
+
storage.upsertThreads([
|
|
33
|
+
{ threadId: "t3", filePath: "/c.txt", fileSize: 300, fileMtime: 3000, cwd: "/proj" },
|
|
34
|
+
]);
|
|
35
|
+
const afterMutationsBuf = readFileSync(dbPath);
|
|
36
|
+
assert.deepEqual(afterMutationsBuf, initialStat, "File should not have been written yet (debounce window has not elapsed)");
|
|
37
|
+
await wait(700);
|
|
38
|
+
const afterDebounceBuf = readFileSync(dbPath);
|
|
39
|
+
assert.notDeepEqual(afterDebounceBuf, initialStat, "File should have been written after debounce window elapsed");
|
|
40
|
+
const stats = storage.getStats();
|
|
41
|
+
assert.equal(stats.totalThreads, 3);
|
|
42
|
+
storage.close();
|
|
43
|
+
});
|
|
43
44
|
it("close() flushes pending changes immediately without waiting for debounce", async () => {
|
|
44
|
-
|
|
45
|
+
dir = makeTmpDir();
|
|
45
46
|
const dbPath = join(dir, "test.db");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
reopened.close();
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
rmSync(dir, { recursive: true, force: true });
|
|
64
|
-
}
|
|
47
|
+
const storage = await MemoryStorage.create(dbPath);
|
|
48
|
+
const initialBuf = readFileSync(dbPath);
|
|
49
|
+
storage.upsertThreads([
|
|
50
|
+
{ threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
|
|
51
|
+
]);
|
|
52
|
+
const beforeCloseBuf = readFileSync(dbPath);
|
|
53
|
+
assert.deepEqual(beforeCloseBuf, initialBuf, "File should not have been written yet (debounce window has not elapsed)");
|
|
54
|
+
storage.close();
|
|
55
|
+
const afterCloseBuf = readFileSync(dbPath);
|
|
56
|
+
assert.notDeepEqual(afterCloseBuf, initialBuf, "File should have been written immediately on close()");
|
|
57
|
+
const reopened = await MemoryStorage.create(dbPath);
|
|
58
|
+
const stats = reopened.getStats();
|
|
59
|
+
assert.equal(stats.totalThreads, 1, "Data should be persisted and readable after close");
|
|
60
|
+
reopened.close();
|
|
65
61
|
});
|
|
66
62
|
});
|
|
67
63
|
//# sourceMappingURL=storage.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../../../src/resources/extensions/memory/storage.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../../../src/resources/extensions/memory/storage.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, readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\nimport { MemoryStorage } from \"./storage.js\";\n\nfunction makeTmpDir(): string {\n\treturn mkdtempSync(join(tmpdir(), \"gsd-memory-storage-test-\"));\n}\n\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\ndescribe(\"MemoryStorage debounced persistence\", () => {\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(\"multiple rapid mutations only trigger one persist write\", async () => {\n\t\tdir = makeTmpDir();\n\t\tconst dbPath = join(dir, \"test.db\");\n\t\tconst storage = await MemoryStorage.create(dbPath);\n\n\t\tconst initialStat = readFileSync(dbPath);\n\t\tconst initialMtime = initialStat.length;\n\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t1\", filePath: \"/a.txt\", fileSize: 100, fileMtime: 1000, cwd: \"/proj\" },\n\t\t]);\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t2\", filePath: \"/b.txt\", fileSize: 200, fileMtime: 2000, cwd: \"/proj\" },\n\t\t]);\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t3\", filePath: \"/c.txt\", fileSize: 300, fileMtime: 3000, cwd: \"/proj\" },\n\t\t]);\n\n\t\tconst afterMutationsBuf = readFileSync(dbPath);\n\t\tassert.deepEqual(\n\t\t\tafterMutationsBuf,\n\t\t\tinitialStat,\n\t\t\t\"File should not have been written yet (debounce window has not elapsed)\",\n\t\t);\n\n\t\tawait wait(700);\n\n\t\tconst afterDebounceBuf = readFileSync(dbPath);\n\t\tassert.notDeepEqual(\n\t\t\tafterDebounceBuf,\n\t\t\tinitialStat,\n\t\t\t\"File should have been written after debounce window elapsed\",\n\t\t);\n\n\t\tconst stats = storage.getStats();\n\t\tassert.equal(stats.totalThreads, 3);\n\n\t\tstorage.close();\n\t});\n\n\tit(\"close() flushes pending changes immediately without waiting for debounce\", async () => {\n\t\tdir = makeTmpDir();\n\t\tconst dbPath = join(dir, \"test.db\");\n\t\tconst storage = await MemoryStorage.create(dbPath);\n\n\t\tconst initialBuf = readFileSync(dbPath);\n\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t1\", filePath: \"/a.txt\", fileSize: 100, fileMtime: 1000, cwd: \"/proj\" },\n\t\t]);\n\n\t\tconst beforeCloseBuf = readFileSync(dbPath);\n\t\tassert.deepEqual(\n\t\t\tbeforeCloseBuf,\n\t\t\tinitialBuf,\n\t\t\t\"File should not have been written yet (debounce window has not elapsed)\",\n\t\t);\n\n\t\tstorage.close();\n\n\t\tconst afterCloseBuf = readFileSync(dbPath);\n\t\tassert.notDeepEqual(\n\t\t\tafterCloseBuf,\n\t\t\tinitialBuf,\n\t\t\t\"File should have been written immediately on close()\",\n\t\t);\n\n\t\tconst reopened = await MemoryStorage.create(dbPath);\n\t\tconst stats = reopened.getStats();\n\t\tassert.equal(stats.totalThreads, 1, \"Data should be persisted and readable after close\");\n\t\treopened.close();\n\t});\n});\n"]}
|
|
@@ -287,7 +287,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
287
287
|
assert.equal(key, undefined);
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
it("falls through to env var when openrouter has type:oauth credential", async () => {
|
|
290
|
+
it("falls through to env var when openrouter has type:oauth credential", async (t) => {
|
|
291
291
|
const storage = inMemory({
|
|
292
292
|
openrouter: {
|
|
293
293
|
type: "oauth",
|
|
@@ -299,17 +299,17 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
299
299
|
|
|
300
300
|
// Simulate OPENROUTER_API_KEY being set via env
|
|
301
301
|
const origEnv = process.env.OPENROUTER_API_KEY;
|
|
302
|
-
|
|
303
|
-
process.env.OPENROUTER_API_KEY = "sk-or-v1-env-key";
|
|
304
|
-
const key = await storage.getApiKey("openrouter");
|
|
305
|
-
assert.equal(key, "sk-or-v1-env-key");
|
|
306
|
-
} finally {
|
|
302
|
+
t.after(() => {
|
|
307
303
|
if (origEnv === undefined) {
|
|
308
304
|
delete process.env.OPENROUTER_API_KEY;
|
|
309
305
|
} else {
|
|
310
306
|
process.env.OPENROUTER_API_KEY = origEnv;
|
|
311
307
|
}
|
|
312
|
-
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
process.env.OPENROUTER_API_KEY = "sk-or-v1-env-key";
|
|
311
|
+
const key = await storage.getApiKey("openrouter");
|
|
312
|
+
assert.equal(key, "sk-or-v1-env-key");
|
|
313
313
|
});
|
|
314
314
|
|
|
315
315
|
it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
|
|
@@ -48,37 +48,37 @@ function makeThrowingExtension(eventType: string, error: Error): Extension {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
describe("ExtensionRunner.emitToolCall", () => {
|
|
51
|
-
it("catches throwing extension handler and routes to emitError", async () => {
|
|
51
|
+
it("catches throwing extension handler and routes to emitError", async (t) => {
|
|
52
52
|
const dir = mkdtempSync(join(tmpdir(), "runner-test-"));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const modelRegistry = new ModelRegistry(authStorage, join(dir, "models.json"));
|
|
53
|
+
t.after(() => {
|
|
54
|
+
rmSync(dir, { recursive: true, force: true });
|
|
55
|
+
});
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const sessionManager = SessionManager.create(dir, dir);
|
|
58
|
+
const authStorage = AuthStorage.create();
|
|
59
|
+
const modelRegistry = new ModelRegistry(authStorage, join(dir, "models.json"));
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const throwingExt = makeThrowingExtension("tool_call", new Error("handler crashed"));
|
|
62
|
+
const runtime = makeMinimalRuntime();
|
|
63
|
+
const runner = new ExtensionRunner([throwingExt], runtime, dir, sessionManager, modelRegistry);
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
toolCallId: "test-123",
|
|
68
|
-
toolName: "test_tool",
|
|
69
|
-
input: {},
|
|
70
|
-
} as ToolCallEvent;
|
|
65
|
+
const errors: any[] = [];
|
|
66
|
+
runner.onError((err) => errors.push(err));
|
|
71
67
|
|
|
72
|
-
|
|
68
|
+
const event: ToolCallEvent = {
|
|
69
|
+
type: "tool_call",
|
|
70
|
+
toolCallId: "test-123",
|
|
71
|
+
toolName: "test_tool",
|
|
72
|
+
input: {},
|
|
73
|
+
} as ToolCallEvent;
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
75
|
+
const result = await runner.emitToolCall(event);
|
|
76
|
+
|
|
77
|
+
// Should not throw — error is caught and routed to emitError
|
|
78
|
+
assert.equal(result, undefined);
|
|
79
|
+
assert.equal(errors.length, 1);
|
|
80
|
+
assert.equal(errors[0].error, "handler crashed");
|
|
81
|
+
assert.equal(errors[0].event, "tool_call");
|
|
82
|
+
assert.equal(errors[0].extensionPath, "/test/throwing-ext");
|
|
83
83
|
});
|
|
84
84
|
});
|
|
@@ -1,66 +1,54 @@
|
|
|
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
|
|
|
8
8
|
describe("atomicWriteFileSync", () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
atomicWriteFileSync(filePath, "hello world");
|
|
14
|
-
assert.equal(readFileSync(filePath, "utf-8"), "hello world");
|
|
15
|
-
} finally {
|
|
9
|
+
let dir: string;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
if (dir) {
|
|
16
13
|
rmSync(dir, { recursive: true, force: true });
|
|
17
14
|
}
|
|
18
15
|
});
|
|
19
16
|
|
|
17
|
+
it("writes file content atomically", () => {
|
|
18
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
19
|
+
const filePath = join(dir, "test.txt");
|
|
20
|
+
atomicWriteFileSync(filePath, "hello world");
|
|
21
|
+
assert.equal(readFileSync(filePath, "utf-8"), "hello world");
|
|
22
|
+
});
|
|
23
|
+
|
|
20
24
|
it("overwrites existing file atomically", () => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
assert.equal(readFileSync(filePath, "utf-8"), "second");
|
|
27
|
-
} finally {
|
|
28
|
-
rmSync(dir, { recursive: true, force: true });
|
|
29
|
-
}
|
|
25
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
26
|
+
const filePath = join(dir, "test.txt");
|
|
27
|
+
atomicWriteFileSync(filePath, "first");
|
|
28
|
+
atomicWriteFileSync(filePath, "second");
|
|
29
|
+
assert.equal(readFileSync(filePath, "utf-8"), "second");
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it("does not leave .tmp file after successful write", () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
assert.equal(existsSync(filePath + ".tmp"), false);
|
|
38
|
-
} finally {
|
|
39
|
-
rmSync(dir, { recursive: true, force: true });
|
|
40
|
-
}
|
|
33
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
34
|
+
const filePath = join(dir, "test.txt");
|
|
35
|
+
atomicWriteFileSync(filePath, "content");
|
|
36
|
+
assert.equal(existsSync(filePath + ".tmp"), false);
|
|
41
37
|
});
|
|
42
38
|
|
|
43
39
|
it("supports Buffer content", () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
assert.deepEqual(result, buf);
|
|
51
|
-
} finally {
|
|
52
|
-
rmSync(dir, { recursive: true, force: true });
|
|
53
|
-
}
|
|
40
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
41
|
+
const filePath = join(dir, "test.bin");
|
|
42
|
+
const buf = Buffer.from([0x00, 0x01, 0x02, 0xff]);
|
|
43
|
+
atomicWriteFileSync(filePath, buf);
|
|
44
|
+
const result = readFileSync(filePath);
|
|
45
|
+
assert.deepEqual(result, buf);
|
|
54
46
|
});
|
|
55
47
|
|
|
56
48
|
it("supports encoding parameter", () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
assert.equal(readFileSync(filePath, "utf-8"), "utf8 content");
|
|
62
|
-
} finally {
|
|
63
|
-
rmSync(dir, { recursive: true, force: true });
|
|
64
|
-
}
|
|
49
|
+
dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
|
|
50
|
+
const filePath = join(dir, "test.txt");
|
|
51
|
+
atomicWriteFileSync(filePath, "utf8 content", "utf-8");
|
|
52
|
+
assert.equal(readFileSync(filePath, "utf-8"), "utf8 content");
|
|
65
53
|
});
|
|
66
54
|
});
|
|
@@ -38,21 +38,20 @@ describe("resolveConfigValue — non-command values", () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
41
|
-
it("blocks a disallowed command and returns undefined", () => {
|
|
41
|
+
it("blocks a disallowed command and returns undefined", (t) => {
|
|
42
42
|
const stderrChunks: string[] = [];
|
|
43
43
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
44
44
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
45
45
|
stderrChunks.push(chunk.toString());
|
|
46
46
|
return true;
|
|
47
47
|
};
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const result = resolveConfigValue("!curl http://evil.com");
|
|
51
|
-
assert.equal(result, undefined);
|
|
52
|
-
assert.ok(stderrChunks.some((line) => line.includes("curl")));
|
|
53
|
-
} finally {
|
|
48
|
+
t.after(() => {
|
|
54
49
|
process.stderr.write = originalWrite;
|
|
55
|
-
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const result = resolveConfigValue("!curl http://evil.com");
|
|
53
|
+
assert.equal(result, undefined);
|
|
54
|
+
assert.ok(stderrChunks.some((line) => line.includes("curl")));
|
|
56
55
|
});
|
|
57
56
|
|
|
58
57
|
it("blocks another disallowed command (rm)", () => {
|
|
@@ -65,7 +64,7 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
|
65
64
|
assert.equal(result, undefined);
|
|
66
65
|
});
|
|
67
66
|
|
|
68
|
-
it("allows a safe command prefix to proceed to execution", () => {
|
|
67
|
+
it("allows a safe command prefix to proceed to execution", (t) => {
|
|
69
68
|
// `pass` is unlikely to be installed in CI, so we just verify it does NOT
|
|
70
69
|
// return undefined due to the allowlist check — it may return undefined if
|
|
71
70
|
// the binary is absent, but the block path must not be taken.
|
|
@@ -76,16 +75,15 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
|
76
75
|
stderrChunks.push(chunk.toString());
|
|
77
76
|
return true;
|
|
78
77
|
};
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
resolveConfigValue("!pass show nonexistent-entry-for-test");
|
|
82
|
-
const blocked = stderrChunks.some((line) =>
|
|
83
|
-
line.includes("Blocked disallowed command")
|
|
84
|
-
);
|
|
85
|
-
assert.equal(blocked, false, "pass should not be blocked by the allowlist");
|
|
86
|
-
} finally {
|
|
78
|
+
t.after(() => {
|
|
87
79
|
process.stderr.write = originalWrite;
|
|
88
|
-
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
resolveConfigValue("!pass show nonexistent-entry-for-test");
|
|
83
|
+
const blocked = stderrChunks.some((line) =>
|
|
84
|
+
line.includes("Blocked disallowed command")
|
|
85
|
+
);
|
|
86
|
+
assert.equal(blocked, false, "pass should not be blocked by the allowlist");
|
|
89
87
|
});
|
|
90
88
|
});
|
|
91
89
|
|
|
@@ -130,61 +128,58 @@ describe("resolveConfigValue — shell operator bypass prevention", () => {
|
|
|
130
128
|
assert.equal(result, undefined);
|
|
131
129
|
});
|
|
132
130
|
|
|
133
|
-
it("writes stderr warning when shell operators detected", () => {
|
|
131
|
+
it("writes stderr warning when shell operators detected", (t) => {
|
|
134
132
|
const stderrChunks: string[] = [];
|
|
135
133
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
136
134
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
137
135
|
stderrChunks.push(chunk.toString());
|
|
138
136
|
return true;
|
|
139
137
|
};
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
resolveConfigValue("!pass show key; curl evil.com");
|
|
143
|
-
assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
|
|
144
|
-
} finally {
|
|
138
|
+
t.after(() => {
|
|
145
139
|
process.stderr.write = originalWrite;
|
|
146
|
-
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
resolveConfigValue("!pass show key; curl evil.com");
|
|
143
|
+
assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
|
|
147
144
|
});
|
|
148
145
|
});
|
|
149
146
|
|
|
150
147
|
describe("resolveConfigValue — caching", () => {
|
|
151
|
-
it("caches the result of a blocked command", () => {
|
|
148
|
+
it("caches the result of a blocked command", (t) => {
|
|
152
149
|
const callCount = { n: 0 };
|
|
153
150
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
154
151
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
155
152
|
callCount.n++;
|
|
156
153
|
return true;
|
|
157
154
|
};
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
resolveConfigValue("!curl http://evil.com");
|
|
161
|
-
resolveConfigValue("!curl http://evil.com");
|
|
162
|
-
// The block warning should only fire once; the second call hits the cache
|
|
163
|
-
// before reaching the allowlist check, so stderr count is 1.
|
|
164
|
-
assert.equal(callCount.n, 1);
|
|
165
|
-
} finally {
|
|
155
|
+
t.after(() => {
|
|
166
156
|
process.stderr.write = originalWrite;
|
|
167
|
-
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
resolveConfigValue("!curl http://evil.com");
|
|
160
|
+
resolveConfigValue("!curl http://evil.com");
|
|
161
|
+
// The block warning should only fire once; the second call hits the cache
|
|
162
|
+
// before reaching the allowlist check, so stderr count is 1.
|
|
163
|
+
assert.equal(callCount.n, 1);
|
|
168
164
|
});
|
|
169
165
|
|
|
170
|
-
it("clearConfigValueCache resets cached entries", () => {
|
|
166
|
+
it("clearConfigValueCache resets cached entries", (t) => {
|
|
171
167
|
const stderrChunks: string[] = [];
|
|
172
168
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
173
169
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
174
170
|
stderrChunks.push(chunk.toString());
|
|
175
171
|
return true;
|
|
176
172
|
};
|
|
173
|
+
t.after(() => {
|
|
174
|
+
process.stderr.write = originalWrite;
|
|
175
|
+
});
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
assert.equal(stderrChunks.length, 1);
|
|
177
|
+
resolveConfigValue("!curl http://evil.com");
|
|
178
|
+
assert.equal(stderrChunks.length, 1);
|
|
181
179
|
|
|
182
|
-
|
|
180
|
+
clearConfigValueCache();
|
|
183
181
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} finally {
|
|
187
|
-
process.stderr.write = originalWrite;
|
|
188
|
-
}
|
|
182
|
+
resolveConfigValue("!curl http://evil.com");
|
|
183
|
+
assert.equal(stderrChunks.length, 2);
|
|
189
184
|
});
|
|
190
185
|
});
|
|
@@ -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";
|
|
@@ -22,44 +22,44 @@ function makeAssistantMessage(input: number, output: number, cacheRead = 0, cach
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
describe("SessionManager usage totals", () => {
|
|
25
|
-
|
|
26
|
-
const dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
27
|
-
try {
|
|
28
|
-
const manager = SessionManager.create(dir, dir);
|
|
29
|
-
|
|
30
|
-
manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] } as any);
|
|
31
|
-
manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
|
|
32
|
-
manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
|
|
25
|
+
let dir: string;
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
output: 9,
|
|
37
|
-
cacheRead: 4,
|
|
38
|
-
cacheWrite: 2,
|
|
39
|
-
cost: 0.35,
|
|
40
|
-
});
|
|
41
|
-
} finally {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (dir) {
|
|
42
29
|
rmSync(dir, { recursive: true, force: true });
|
|
43
30
|
}
|
|
44
31
|
});
|
|
45
32
|
|
|
33
|
+
it("tracks assistant usage incrementally without rescanning entries", () => {
|
|
34
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
35
|
+
const manager = SessionManager.create(dir, dir);
|
|
36
|
+
|
|
37
|
+
manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] } as any);
|
|
38
|
+
manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
|
|
39
|
+
manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
|
|
40
|
+
|
|
41
|
+
assert.deepEqual(manager.getUsageTotals(), {
|
|
42
|
+
input: 17,
|
|
43
|
+
output: 9,
|
|
44
|
+
cacheRead: 4,
|
|
45
|
+
cacheWrite: 2,
|
|
46
|
+
cost: 0.35,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
46
50
|
it("resets totals when starting a new session", () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
assert.equal(manager.getUsageTotals().input, 5);
|
|
51
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
52
|
+
const manager = SessionManager.create(dir, dir);
|
|
53
|
+
manager.appendMessage(makeAssistantMessage(5, 5, 0, 0, 0.05));
|
|
54
|
+
assert.equal(manager.getUsageTotals().input, 5);
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} finally {
|
|
62
|
-
rmSync(dir, { recursive: true, force: true });
|
|
63
|
-
}
|
|
56
|
+
manager.newSession();
|
|
57
|
+
assert.deepEqual(manager.getUsageTotals(), {
|
|
58
|
+
input: 0,
|
|
59
|
+
output: 0,
|
|
60
|
+
cacheRead: 0,
|
|
61
|
+
cacheWrite: 0,
|
|
62
|
+
cost: 0,
|
|
63
|
+
});
|
|
64
64
|
});
|
|
65
65
|
});
|
|
@@ -60,26 +60,26 @@ describe("edit-diff", () => {
|
|
|
60
60
|
assert.match(result.diff, /CHANGED/);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it("computes diffs for preview without native helpers", async () => {
|
|
63
|
+
it("computes diffs for preview without native helpers", async (t) => {
|
|
64
64
|
const dir = mkdtempSync(join(tmpdir(), "edit-diff-test-"));
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
t.after(() => {
|
|
66
|
+
rmSync(dir, { recursive: true, force: true });
|
|
67
|
+
});
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"const title = \"Hello\";\n",
|
|
72
|
-
"const title = \"Hi\";\n",
|
|
73
|
-
dir,
|
|
74
|
-
);
|
|
69
|
+
const file = join(dir, "sample.ts");
|
|
70
|
+
writeFileSync(file, "const title = “Hello”;\n", "utf-8");
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
const result = await computeEditDiff(
|
|
73
|
+
file,
|
|
74
|
+
"const title = \"Hello\";\n",
|
|
75
|
+
"const title = \"Hi\";\n",
|
|
76
|
+
dir,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
assert.ok(!("error" in result), "expected a diff result");
|
|
80
|
+
if (!("error" in result)) {
|
|
81
|
+
assert.equal(result.firstChangedLine, 1);
|
|
82
|
+
assert.match(result.diff, /\+1 const title = "Hi";/);
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
});
|