@cloudcli-ai/cloudcli 1.37.0 → 1.37.2
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/dist/assets/abnfDiagram-N423BO3Z-C4UAoHJb.js +1 -0
- package/dist/assets/arc-B8jF-ja9.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-C_Wur_CU.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-Cq4P1i8t.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-C1wG4eUj.js +10 -0
- package/dist/assets/channel-_falYbU1.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-B8fnBG6E.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-WdfPooPJ.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-DhZJuG2E.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-B0rjslal.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-CG2Oj4jL.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rrl2lRxY.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CjVbrVMA.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-CRzKgU8S.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-DBz4GTZF.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-wHxalsXk.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-s1hygY8e.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-C4LGV15i.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-C5uhu7B3.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-B-OBTewm.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BjGKQxih.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-BMKz1X2L.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DNh-GhKC.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-BaTG9jPT.js +24 -0
- package/dist/assets/diagram-UB23O5K3-BUwo2OvJ.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DAZgfzX2.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-1w1CcGEb.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-D4EII8Jj.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-ChvJZAgB.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-DtOn41S8.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-CzPLBvXT.css +32 -0
- package/dist/assets/index-DFkBBuSA.js +1549 -0
- package/dist/assets/infoDiagram-6WML65LV-DN_PVEJZ.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DbkF1kXb.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-pJwmhddE.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-PPAPgNp5.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-iPR-J9f7.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-BEUL86Z6.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-BvZoBib7.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-DhYreaUd.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CrOHlyD0.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-D4g5yH3t.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-Dj39lHlf.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-1AS4Zgm_.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-CKlMn19P.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-B_j3qyvo.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Bp2Op16v.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-C0cN6DoY.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-BEvXOV9b.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-zdJQinvZ.js +120 -0
- package/dist/assets/vennDiagram-T6HMQDX7-DuF1uZMY.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-B9AlLQup.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DTJX48Qg.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +1 -2
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/commands/commands.routes.js +3 -3
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js +2 -7
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
- package/dist-server/server/modules/database/index.js +2 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +18 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +63 -7
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +28 -3
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js +5 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +60 -20
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +143 -16
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +46 -14
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +259 -60
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +58 -71
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +7 -7
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +32 -669
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +105 -328
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +144 -31
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +167 -199
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +2 -2
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +75 -11
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +53 -17
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +175 -292
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +2 -6
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +5 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/shared/utils.js +8 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +8 -4
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +1 -2
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/commands/commands.routes.ts +3 -3
- package/server/modules/commands/tests/commands.test.ts +2 -7
- package/server/modules/database/index.ts +2 -0
- package/server/modules/database/migrations.ts +20 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/sessions.db.ts +85 -8
- package/server/modules/database/schema.ts +29 -3
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
- package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
- package/server/modules/file-tree/file-tree.module.ts +5 -1
- package/server/modules/file-tree/file-tree.service.ts +81 -22
- package/server/modules/file-tree/tests/file-tree.service.test.ts +167 -16
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/providers/list/claude/claude-models.provider.ts +46 -14
- package/server/modules/providers/list/claude/claude-runtime.provider.js +280 -67
- package/server/modules/providers/list/codex/codex-models.provider.ts +58 -99
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +7 -7
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -710
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +105 -413
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
- package/server/modules/providers/provider.routes.ts +190 -33
- package/server/modules/providers/services/provider-models.service.ts +223 -288
- package/server/modules/providers/services/provider-runtime.service.ts +2 -3
- package/server/modules/providers/services/session-conversations-search.service.ts +106 -11
- package/server/modules/providers/services/sessions.service.ts +87 -3
- package/server/modules/providers/tests/codex-sessions.test.ts +57 -18
- package/server/modules/providers/tests/opencode-models.test.ts +34 -135
- package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -450
- package/server/modules/providers/tests/provider-runtime.service.test.ts +2 -6
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +1 -0
- package/server/modules/providers/tests/provider.routes.test.ts +244 -0
- package/server/modules/providers/tests/sessions.service.test.ts +127 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +5 -1
- package/server/shared/interfaces.ts +4 -6
- package/server/shared/types.ts +29 -18
- package/server/shared/utils.ts +10 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.md +0 -269
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-DSlds633.css +0 -32
- package/dist/assets/index-eGqPeIEu.js +0 -1531
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
|
@@ -19,6 +19,18 @@ function createDirectoryEntry(name: string, directory: boolean): FileTreeDirecto
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Adapts a path-keyed listing to the streaming directory contract so tests keep
|
|
24
|
+
* describing directories as plain arrays.
|
|
25
|
+
*/
|
|
26
|
+
function createDirectoryReader(
|
|
27
|
+
listDirectory: (directoryPath: string) => FileTreeDirectoryEntry[],
|
|
28
|
+
): FileTreeFileSystem['openDirectory'] {
|
|
29
|
+
return async function* openDirectory(directoryPath) {
|
|
30
|
+
yield* listDirectory(directoryPath);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
function createStats(directory: boolean, mode: number): FileTreeStats {
|
|
23
35
|
return {
|
|
24
36
|
size: directory ? 0 : 24,
|
|
@@ -40,7 +52,9 @@ function createFakeFileSystem(
|
|
|
40
52
|
access: unexpectedOperation,
|
|
41
53
|
stat: unexpectedOperation,
|
|
42
54
|
lstat: unexpectedOperation,
|
|
43
|
-
|
|
55
|
+
openDirectory: () => ({
|
|
56
|
+
[Symbol.asyncIterator]: () => ({ next: unexpectedOperation }),
|
|
57
|
+
}),
|
|
44
58
|
realpath: unexpectedOperation,
|
|
45
59
|
readTextFile: unexpectedOperation,
|
|
46
60
|
writeTextFile: unexpectedOperation,
|
|
@@ -73,37 +87,67 @@ function createDependencies(
|
|
|
73
87
|
};
|
|
74
88
|
}
|
|
75
89
|
|
|
76
|
-
test('listProjectFiles
|
|
90
|
+
test('listProjectFiles applies gitignore alongside hard directory exclusions', async () => {
|
|
77
91
|
const projectRoot = path.resolve('file-tree-test-project');
|
|
92
|
+
const documentationDirectory = path.join(projectRoot, 'docs');
|
|
93
|
+
const buildDocumentationDirectory = path.join(documentationDirectory, 'build');
|
|
94
|
+
const gitDirectory = path.join(projectRoot, '.git');
|
|
95
|
+
const nodeModulesDirectory = path.join(projectRoot, 'node_modules');
|
|
78
96
|
const sourceDirectory = path.join(projectRoot, 'src');
|
|
97
|
+
const readDirectories: string[] = [];
|
|
79
98
|
const fileSystem = createFakeFileSystem({
|
|
80
99
|
access: async () => undefined,
|
|
81
|
-
|
|
100
|
+
readTextFile: async (filePath) => {
|
|
101
|
+
assert.equal(filePath, path.join(projectRoot, '.gitignore'));
|
|
102
|
+
return '*.log';
|
|
103
|
+
},
|
|
104
|
+
openDirectory: createDirectoryReader((directoryPath) => {
|
|
105
|
+
readDirectories.push(directoryPath);
|
|
82
106
|
if (directoryPath === projectRoot) {
|
|
83
107
|
return [
|
|
108
|
+
createDirectoryEntry('.git', true),
|
|
84
109
|
createDirectoryEntry('node_modules', true),
|
|
85
110
|
createDirectoryEntry('README.md', false),
|
|
111
|
+
createDirectoryEntry('docs', true),
|
|
86
112
|
createDirectoryEntry('src', true),
|
|
87
113
|
];
|
|
88
114
|
}
|
|
115
|
+
if (directoryPath === documentationDirectory) {
|
|
116
|
+
return [createDirectoryEntry('build', true)];
|
|
117
|
+
}
|
|
118
|
+
if (directoryPath === buildDocumentationDirectory) {
|
|
119
|
+
return [createDirectoryEntry('foo.md', false)];
|
|
120
|
+
}
|
|
89
121
|
if (directoryPath === sourceDirectory) {
|
|
90
122
|
return [createDirectoryEntry('index.ts', false)];
|
|
91
123
|
}
|
|
92
124
|
return [];
|
|
93
|
-
},
|
|
94
|
-
lstat: async (candidatePath) => createStats(
|
|
125
|
+
}),
|
|
126
|
+
lstat: async (candidatePath) => createStats(
|
|
127
|
+
candidatePath === documentationDirectory
|
|
128
|
+
|| candidatePath === buildDocumentationDirectory
|
|
129
|
+
|| candidatePath === sourceDirectory
|
|
130
|
+
|| candidatePath === gitDirectory
|
|
131
|
+
|| candidatePath === nodeModulesDirectory,
|
|
132
|
+
0o754,
|
|
133
|
+
),
|
|
95
134
|
});
|
|
96
135
|
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
97
136
|
|
|
98
|
-
const tree = await service.listProjectFiles('project-1');
|
|
137
|
+
const tree = await service.listProjectFiles('project-1', { respectGitignore: true });
|
|
99
138
|
|
|
100
|
-
assert.deepEqual(tree.map((entry) => entry.name), ['src', 'README.md']);
|
|
101
|
-
const
|
|
139
|
+
assert.deepEqual(tree.map((entry) => entry.name), ['docs', 'src', 'README.md']);
|
|
140
|
+
const documentationEntry = tree[0];
|
|
141
|
+
assert.deepEqual(documentationEntry?.children?.map((entry) => entry.name), ['build']);
|
|
142
|
+
assert.deepEqual(documentationEntry?.children?.[0]?.children?.map((entry) => entry.name), ['foo.md']);
|
|
143
|
+
const sourceEntry = tree[1];
|
|
102
144
|
assert.ok(sourceEntry);
|
|
103
145
|
assert.equal(sourceEntry.type, 'directory');
|
|
104
146
|
assert.equal(sourceEntry.permissions, '754');
|
|
105
147
|
assert.equal(sourceEntry.permissionsRwx, 'rwxr-xr--');
|
|
106
148
|
assert.deepEqual(sourceEntry.children?.map((entry) => entry.name), ['index.ts']);
|
|
149
|
+
assert.equal(readDirectories.includes(gitDirectory), false);
|
|
150
|
+
assert.equal(readDirectories.includes(nodeModulesDirectory), false);
|
|
107
151
|
});
|
|
108
152
|
|
|
109
153
|
test('listProjectFiles excludes gitignored entries only when requested', async () => {
|
|
@@ -117,7 +161,7 @@ test('listProjectFiles excludes gitignored entries only when requested', async (
|
|
|
117
161
|
assert.equal(filePath, path.join(projectRoot, '.gitignore'));
|
|
118
162
|
return ['*.log', '!keep.log', 'cache/', 'src/generated.ts'].join('\n');
|
|
119
163
|
},
|
|
120
|
-
|
|
164
|
+
openDirectory: createDirectoryReader((directoryPath) => {
|
|
121
165
|
readDirectories.push(directoryPath);
|
|
122
166
|
if (directoryPath === projectRoot) {
|
|
123
167
|
return [
|
|
@@ -138,7 +182,7 @@ test('listProjectFiles excludes gitignored entries only when requested', async (
|
|
|
138
182
|
];
|
|
139
183
|
}
|
|
140
184
|
return [];
|
|
141
|
-
},
|
|
185
|
+
}),
|
|
142
186
|
lstat: async (candidatePath) => createStats(
|
|
143
187
|
candidatePath === cacheDirectory || candidatePath === sourceDirectory,
|
|
144
188
|
0o644,
|
|
@@ -153,23 +197,130 @@ test('listProjectFiles excludes gitignored entries only when requested', async (
|
|
|
153
197
|
assert.equal(readDirectories.includes(cacheDirectory), false);
|
|
154
198
|
});
|
|
155
199
|
|
|
156
|
-
test('listProjectFiles
|
|
200
|
+
test('listProjectFiles falls back to conventional directory names when no gitignore exists', async () => {
|
|
157
201
|
const projectRoot = path.resolve('file-tree-test-project');
|
|
202
|
+
const documentationDirectory = path.join(projectRoot, 'docs');
|
|
203
|
+
const buildDocumentationDirectory = path.join(documentationDirectory, 'build');
|
|
204
|
+
const nodeModulesDirectory = path.join(projectRoot, 'node_modules');
|
|
205
|
+
const readDirectories: string[] = [];
|
|
158
206
|
const fileSystem = createFakeFileSystem({
|
|
159
207
|
access: async () => undefined,
|
|
160
208
|
readTextFile: async () => {
|
|
161
209
|
throw Object.assign(new Error('missing'), { code: 'ENOENT' });
|
|
162
210
|
},
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
211
|
+
openDirectory: createDirectoryReader((directoryPath) => {
|
|
212
|
+
readDirectories.push(directoryPath);
|
|
213
|
+
if (directoryPath === projectRoot) {
|
|
214
|
+
return [
|
|
215
|
+
createDirectoryEntry('debug.log', false),
|
|
216
|
+
createDirectoryEntry('docs', true),
|
|
217
|
+
createDirectoryEntry('node_modules', true),
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
if (directoryPath === documentationDirectory) {
|
|
221
|
+
return [
|
|
222
|
+
createDirectoryEntry('build', true),
|
|
223
|
+
createDirectoryEntry('guide.md', false),
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
if (directoryPath === buildDocumentationDirectory) {
|
|
227
|
+
return [createDirectoryEntry('generated.md', false)];
|
|
228
|
+
}
|
|
229
|
+
return [];
|
|
230
|
+
}),
|
|
231
|
+
lstat: async (candidatePath) => createStats(candidatePath === documentationDirectory, 0o644),
|
|
167
232
|
});
|
|
168
233
|
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
169
234
|
|
|
170
235
|
const tree = await service.listProjectFiles('project-1', { respectGitignore: true });
|
|
171
236
|
|
|
172
|
-
assert.deepEqual(tree.map((entry) => entry.name), ['debug.log']);
|
|
237
|
+
assert.deepEqual(tree.map((entry) => entry.name), ['docs', 'debug.log']);
|
|
238
|
+
assert.deepEqual(tree[0]?.children?.map((entry) => entry.name), ['guide.md']);
|
|
239
|
+
assert.equal(readDirectories.includes(nodeModulesDirectory), false);
|
|
240
|
+
assert.equal(readDirectories.includes(buildDocumentationDirectory), false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('listProjectFiles rejects a tree that exceeds the server entry limit', async () => {
|
|
244
|
+
const projectRoot = path.resolve('file-tree-test-project');
|
|
245
|
+
const fileSystem = createFakeFileSystem({
|
|
246
|
+
access: async () => undefined,
|
|
247
|
+
openDirectory: createDirectoryReader((directoryPath) => directoryPath === projectRoot
|
|
248
|
+
? Array.from({ length: 10_001 }, (_, index) => createDirectoryEntry(`file-${index}.txt`, false))
|
|
249
|
+
: []),
|
|
250
|
+
lstat: async () => createStats(false, 0o644),
|
|
251
|
+
});
|
|
252
|
+
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
253
|
+
|
|
254
|
+
await assert.rejects(
|
|
255
|
+
service.listProjectFiles('project-1'),
|
|
256
|
+
(error: unknown) => error instanceof AppError
|
|
257
|
+
&& error.code === 'FILE_TREE_TOO_LARGE'
|
|
258
|
+
&& error.statusCode === 413,
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('listProjectFiles abandons a directory stream as soon as the entry limit is passed', async () => {
|
|
263
|
+
const projectRoot = path.resolve('file-tree-test-project');
|
|
264
|
+
let streamedEntries = 0;
|
|
265
|
+
const fileSystem = createFakeFileSystem({
|
|
266
|
+
access: async () => undefined,
|
|
267
|
+
// Endless on purpose: the walk has to stop consuming the stream itself
|
|
268
|
+
// instead of waiting for the directory listing to be materialized.
|
|
269
|
+
openDirectory: async function* (directoryPath) {
|
|
270
|
+
if (directoryPath !== projectRoot) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
for (let index = 0; ; index += 1) {
|
|
274
|
+
streamedEntries += 1;
|
|
275
|
+
yield createDirectoryEntry(`file-${index}.txt`, false);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
lstat: async () => createStats(false, 0o644),
|
|
279
|
+
});
|
|
280
|
+
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
281
|
+
|
|
282
|
+
await assert.rejects(
|
|
283
|
+
service.listProjectFiles('project-1'),
|
|
284
|
+
(error: unknown) => error instanceof AppError
|
|
285
|
+
&& error.code === 'FILE_TREE_TOO_LARGE'
|
|
286
|
+
&& error.statusCode === 413,
|
|
287
|
+
);
|
|
288
|
+
// The budget plus the single entry that proves it was exceeded.
|
|
289
|
+
assert.equal(streamedEntries, 10_001);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('listProjectFiles shares the entry limit across nested directories', async () => {
|
|
293
|
+
const projectRoot = path.resolve('file-tree-test-project');
|
|
294
|
+
const firstDirectory = path.join(projectRoot, 'first');
|
|
295
|
+
const secondDirectory = path.join(projectRoot, 'second');
|
|
296
|
+
const directoryPaths = new Set([firstDirectory, secondDirectory]);
|
|
297
|
+
const fileSystem = createFakeFileSystem({
|
|
298
|
+
access: async () => undefined,
|
|
299
|
+
openDirectory: createDirectoryReader((directoryPath) => {
|
|
300
|
+
if (directoryPath === projectRoot) {
|
|
301
|
+
return [
|
|
302
|
+
createDirectoryEntry('first', true),
|
|
303
|
+
createDirectoryEntry('second', true),
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
if (directoryPaths.has(directoryPath)) {
|
|
307
|
+
return Array.from(
|
|
308
|
+
{ length: 5_000 },
|
|
309
|
+
(_, index) => createDirectoryEntry(`${path.basename(directoryPath)}-${index}.txt`, false),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return [];
|
|
313
|
+
}),
|
|
314
|
+
lstat: async (candidatePath) => createStats(directoryPaths.has(candidatePath), 0o644),
|
|
315
|
+
});
|
|
316
|
+
const service = createFileTreeService(createDependencies(fileSystem, projectRoot));
|
|
317
|
+
|
|
318
|
+
await assert.rejects(
|
|
319
|
+
service.listProjectFiles('project-1'),
|
|
320
|
+
(error: unknown) => error instanceof AppError
|
|
321
|
+
&& error.code === 'FILE_TREE_TOO_LARGE'
|
|
322
|
+
&& error.statusCode === 413,
|
|
323
|
+
);
|
|
173
324
|
});
|
|
174
325
|
|
|
175
326
|
test('readTextFile rejects traversal before invoking the filesystem adapter', async () => {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AppError } from '@/shared/utils.js';
|
|
2
|
+
|
|
3
|
+
type GitCommandResult = {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type GitCommandRunner = (
|
|
9
|
+
command: string,
|
|
10
|
+
args: string[],
|
|
11
|
+
options: { cwd: string },
|
|
12
|
+
) => Promise<GitCommandResult>;
|
|
13
|
+
|
|
14
|
+
type DeleteLocalBranchInput = {
|
|
15
|
+
projectPath: string;
|
|
16
|
+
branch: string;
|
|
17
|
+
force: boolean;
|
|
18
|
+
runCommand: GitCommandRunner;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Used by the Git routes module to safely delete a non-current local branch. */
|
|
22
|
+
export async function deleteLocalBranch(input: DeleteLocalBranchInput): Promise<string> {
|
|
23
|
+
const { stdout: currentBranch } = await input.runCommand(
|
|
24
|
+
'git',
|
|
25
|
+
['branch', '--show-current'],
|
|
26
|
+
{ cwd: input.projectPath },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (currentBranch.trim() === input.branch) {
|
|
30
|
+
throw new AppError('Cannot delete the currently checked-out branch', {
|
|
31
|
+
code: 'GIT_CURRENT_BRANCH_DELETE',
|
|
32
|
+
statusCode: 400,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const deleteFlag = input.force ? '-D' : '-d';
|
|
37
|
+
const { stdout } = await input.runCommand(
|
|
38
|
+
'git',
|
|
39
|
+
['branch', deleteFlag, '--', input.branch],
|
|
40
|
+
{ cwd: input.projectPath },
|
|
41
|
+
);
|
|
42
|
+
return stdout;
|
|
43
|
+
}
|
|
@@ -8,6 +8,7 @@ import { AppError } from '@/shared/utils.js';
|
|
|
8
8
|
|
|
9
9
|
// cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution.
|
|
10
10
|
import { parseGitLogWithStats, parseGitStatusOutput } from './git-parsing.service.js';
|
|
11
|
+
import { deleteLocalBranch } from './git-branch.service.js';
|
|
11
12
|
|
|
12
13
|
type GitRouterDependencies = {
|
|
13
14
|
fileSystem: typeof import('node:fs/promises');
|
|
@@ -839,26 +840,32 @@ router.post('/create-branch', async (req, res) => {
|
|
|
839
840
|
|
|
840
841
|
// Delete a local branch
|
|
841
842
|
router.post('/delete-branch', async (req, res) => {
|
|
842
|
-
const { project, branch } = req.body;
|
|
843
|
+
const { project, branch, force = false } = req.body;
|
|
843
844
|
|
|
844
845
|
if (!project || !branch) {
|
|
845
846
|
return res.status(400).json({ error: 'Project id and branch name are required' });
|
|
846
847
|
}
|
|
847
848
|
|
|
849
|
+
if (typeof force !== 'boolean') {
|
|
850
|
+
return res.status(400).json({ error: 'force must be a boolean' });
|
|
851
|
+
}
|
|
852
|
+
|
|
848
853
|
try {
|
|
849
854
|
const projectPath = await getActualProjectPath(project);
|
|
850
855
|
await validateGitRepository(projectPath);
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
const { stdout } = await spawnAsync('git', ['branch', '-d', branch], { cwd: projectPath });
|
|
856
|
+
validateBranchName(branch);
|
|
857
|
+
const stdout = await deleteLocalBranch({
|
|
858
|
+
projectPath,
|
|
859
|
+
branch,
|
|
860
|
+
force,
|
|
861
|
+
runCommand: spawnAsync,
|
|
862
|
+
});
|
|
859
863
|
res.json({ success: true, output: stdout });
|
|
860
864
|
} catch (error) {
|
|
861
865
|
console.error('Git delete branch error:', error);
|
|
866
|
+
if (error instanceof AppError) {
|
|
867
|
+
return res.status(error.statusCode).json({ error: error.message });
|
|
868
|
+
}
|
|
862
869
|
res.status(500).json({ error: error.message });
|
|
863
870
|
}
|
|
864
871
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { deleteLocalBranch } from '../git-branch.service.js';
|
|
5
|
+
|
|
6
|
+
test('deleteLocalBranch uses safe deletion by default', async () => {
|
|
7
|
+
const commands: string[][] = [];
|
|
8
|
+
const output = await deleteLocalBranch({
|
|
9
|
+
projectPath: '/workspace/repo',
|
|
10
|
+
branch: 'feature/safe-delete',
|
|
11
|
+
force: false,
|
|
12
|
+
runCommand: async (_command, args) => {
|
|
13
|
+
commands.push(args);
|
|
14
|
+
return {
|
|
15
|
+
stdout: args.includes('--show-current') ? 'main\n' : 'Deleted branch feature/safe-delete.\n',
|
|
16
|
+
stderr: '',
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
assert.equal(output, 'Deleted branch feature/safe-delete.\n');
|
|
22
|
+
assert.deepEqual(commands, [
|
|
23
|
+
['branch', '--show-current'],
|
|
24
|
+
['branch', '-d', '--', 'feature/safe-delete'],
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('deleteLocalBranch uses force deletion only when requested', async () => {
|
|
29
|
+
const commands: string[][] = [];
|
|
30
|
+
await deleteLocalBranch({
|
|
31
|
+
projectPath: '/workspace/repo',
|
|
32
|
+
branch: 'feature/unmerged',
|
|
33
|
+
force: true,
|
|
34
|
+
runCommand: async (_command, args) => {
|
|
35
|
+
commands.push(args);
|
|
36
|
+
return { stdout: args.includes('--show-current') ? 'main\n' : '', stderr: '' };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
assert.deepEqual(commands, [
|
|
41
|
+
['branch', '--show-current'],
|
|
42
|
+
['branch', '-D', '--', 'feature/unmerged'],
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('deleteLocalBranch refuses to delete the current branch even when forced', async () => {
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
deleteLocalBranch({
|
|
49
|
+
projectPath: '/workspace/repo',
|
|
50
|
+
branch: 'feature/current',
|
|
51
|
+
force: true,
|
|
52
|
+
runCommand: async () => ({ stdout: 'feature/current\n', stderr: '' }),
|
|
53
|
+
}),
|
|
54
|
+
(error: unknown) => {
|
|
55
|
+
assert.equal((error as { code?: string }).code, 'GIT_CURRENT_BRANCH_DELETE');
|
|
56
|
+
assert.equal((error as { statusCode?: number }).statusCode, 400);
|
|
57
|
+
return true;
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -50,3 +50,86 @@ test('git init does not run when repository validation fails for an execution er
|
|
|
50
50
|
|
|
51
51
|
assert.deepEqual(commands, [['rev-parse', '--is-inside-work-tree']]);
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
test('delete branch parses force and uses Git force deletion', async () => {
|
|
55
|
+
const commands: string[][] = [];
|
|
56
|
+
const spawnProcess = ((_command: string, args: string[]) => {
|
|
57
|
+
commands.push(args);
|
|
58
|
+
const child = new EventEmitter() as EventEmitter & { stdout: PassThrough; stderr: PassThrough };
|
|
59
|
+
child.stdout = new PassThrough();
|
|
60
|
+
child.stderr = new PassThrough();
|
|
61
|
+
process.nextTick(() => {
|
|
62
|
+
if (args.includes('--is-inside-work-tree')) child.stdout.write('true\n');
|
|
63
|
+
if (args.includes('--show-toplevel')) child.stdout.write('/workspace/repo\n');
|
|
64
|
+
if (args.includes('--show-current')) child.stdout.write('main\n');
|
|
65
|
+
if (args.includes('-D')) child.stdout.write('Deleted branch feature/unmerged.\n');
|
|
66
|
+
child.stdout.end();
|
|
67
|
+
child.stderr.end();
|
|
68
|
+
child.emit('close', 0);
|
|
69
|
+
});
|
|
70
|
+
return child;
|
|
71
|
+
}) as Parameters<typeof createGitRouter>[0]['spawnProcess'];
|
|
72
|
+
const unexpectedProvider = async (): Promise<never> => { throw new Error('unexpected provider call'); };
|
|
73
|
+
const router = createGitRouter({
|
|
74
|
+
fileSystem: { access: async () => undefined } as unknown as Parameters<typeof createGitRouter>[0]['fileSystem'],
|
|
75
|
+
spawnProcess,
|
|
76
|
+
resolveProjectPathById: () => '/workspace/repo',
|
|
77
|
+
queryClaude: unexpectedProvider,
|
|
78
|
+
queryCursor: unexpectedProvider,
|
|
79
|
+
});
|
|
80
|
+
const app = express();
|
|
81
|
+
app.use(express.json());
|
|
82
|
+
app.use('/api/git', router);
|
|
83
|
+
const server = app.listen(0, '127.0.0.1');
|
|
84
|
+
await once(server, 'listening');
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const address = server.address() as AddressInfo;
|
|
88
|
+
const response = await fetch(`http://127.0.0.1:${address.port}/api/git/delete-branch`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'content-type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ project: 'project-1', branch: 'feature/unmerged', force: true }),
|
|
92
|
+
});
|
|
93
|
+
const body = await response.json() as { success: boolean; output: string };
|
|
94
|
+
assert.equal(response.status, 200);
|
|
95
|
+
assert.equal(body.success, true);
|
|
96
|
+
assert.match(body.output, /Deleted branch/);
|
|
97
|
+
} finally {
|
|
98
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
assert.deepEqual(commands.at(-1), ['branch', '-D', '--', 'feature/unmerged']);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('delete branch rejects a non-boolean force value before running Git', async () => {
|
|
105
|
+
const spawnProcess = (() => {
|
|
106
|
+
throw new Error('Git must not run for invalid input');
|
|
107
|
+
}) as Parameters<typeof createGitRouter>[0]['spawnProcess'];
|
|
108
|
+
const unexpectedProvider = async (): Promise<never> => { throw new Error('unexpected provider call'); };
|
|
109
|
+
const router = createGitRouter({
|
|
110
|
+
fileSystem: { access: async () => undefined } as unknown as Parameters<typeof createGitRouter>[0]['fileSystem'],
|
|
111
|
+
spawnProcess,
|
|
112
|
+
resolveProjectPathById: () => '/workspace/repo',
|
|
113
|
+
queryClaude: unexpectedProvider,
|
|
114
|
+
queryCursor: unexpectedProvider,
|
|
115
|
+
});
|
|
116
|
+
const app = express();
|
|
117
|
+
app.use(express.json());
|
|
118
|
+
app.use('/api/git', router);
|
|
119
|
+
const server = app.listen(0, '127.0.0.1');
|
|
120
|
+
await once(server, 'listening');
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const address = server.address() as AddressInfo;
|
|
124
|
+
const response = await fetch(`http://127.0.0.1:${address.port}/api/git/delete-branch`, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'content-type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({ project: 'project-1', branch: 'feature/unmerged', force: 'yes' }),
|
|
128
|
+
});
|
|
129
|
+
const body = await response.json() as { error: string };
|
|
130
|
+
assert.equal(response.status, 400);
|
|
131
|
+
assert.equal(body.error, 'force must be a boolean');
|
|
132
|
+
} finally {
|
|
133
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
@@ -9,6 +9,8 @@ export {
|
|
|
9
9
|
notifyRunFailed,
|
|
10
10
|
// Used by provider runtimes to report stopped or completed agent runs.
|
|
11
11
|
notifyRunStopped,
|
|
12
|
+
// Used by provider runtimes to report background work that finished after its turn ended.
|
|
13
|
+
notifyBackgroundWorkCompleted,
|
|
12
14
|
} from '@/modules/notifications/services/notification-orchestrator.service.js';
|
|
13
15
|
export {
|
|
14
16
|
registerDesktopNotificationClient,
|
|
@@ -155,6 +155,7 @@ function buildNotificationPayload(event) {
|
|
|
155
155
|
? `Action Required: Tool "${normalizedEvent.meta.toolName}" needs approval`
|
|
156
156
|
: 'Action Required: A tool needs your approval',
|
|
157
157
|
'run.stopped': normalizedEvent.meta?.stopReason || 'Run Stopped: The run has stopped',
|
|
158
|
+
'run.background_completed': 'Background work finished',
|
|
158
159
|
'run.failed': normalizedEvent.meta?.error ? `Run Failed: ${normalizedEvent.meta.error}` : 'Run Failed: The run encountered an error',
|
|
159
160
|
'agent.notification': normalizedEvent.meta?.message ? String(normalizedEvent.meta.message) : 'You have a new notification',
|
|
160
161
|
'push.enabled': 'Push notifications are now enabled!'
|
|
@@ -261,6 +262,27 @@ function notifyRunStopped({ userId, provider, sessionId = null, stopReason = 'co
|
|
|
261
262
|
});
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Reports background work that finished after its turn had already completed.
|
|
267
|
+
*
|
|
268
|
+
* Uses the `stop` kind so it rides the existing "run stopped" preference rather
|
|
269
|
+
* than needing a new opt-in that would default to off. No explicit dedupeKey, so
|
|
270
|
+
* the default composite key collapses repeats inside the dedupe window.
|
|
271
|
+
*/
|
|
272
|
+
function notifyBackgroundWorkCompleted({ userId, provider, sessionId = null, sessionName = null }) {
|
|
273
|
+
notifyUserIfEnabled({
|
|
274
|
+
userId,
|
|
275
|
+
event: createNotificationEvent({
|
|
276
|
+
provider,
|
|
277
|
+
sessionId,
|
|
278
|
+
kind: 'stop',
|
|
279
|
+
code: 'run.background_completed',
|
|
280
|
+
meta: { sessionName },
|
|
281
|
+
severity: 'info'
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
264
286
|
function notifyRunFailed({ userId, provider, sessionId = null, error, sessionName = null }) {
|
|
265
287
|
const errorMessage = normalizeErrorMessage(error);
|
|
266
288
|
|
|
@@ -283,5 +305,6 @@ export {
|
|
|
283
305
|
createNotificationEvent,
|
|
284
306
|
notifyUserIfEnabled,
|
|
285
307
|
notifyRunStopped,
|
|
286
|
-
notifyRunFailed
|
|
308
|
+
notifyRunFailed,
|
|
309
|
+
notifyBackgroundWorkCompleted
|
|
287
310
|
};
|