teamcopilot 0.0.1

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.
Files changed (209) hide show
  1. package/.env.example +10 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +131 -0
  4. package/bin/teamcopilot.js +281 -0
  5. package/dist/auth/index.js +189 -0
  6. package/dist/change-user-role.js +77 -0
  7. package/dist/chat/index.js +849 -0
  8. package/dist/constants.js +2 -0
  9. package/dist/create-user.js +98 -0
  10. package/dist/cronjob/index.js +16 -0
  11. package/dist/cronjob/resource-reconciliation.js +33 -0
  12. package/dist/delete-user.js +66 -0
  13. package/dist/frontend/assets/abap-CRCWOmpq.js +1 -0
  14. package/dist/frontend/assets/apex-DnsZk_dE.js +1 -0
  15. package/dist/frontend/assets/azcli-1IWB1ccx.js +1 -0
  16. package/dist/frontend/assets/bat-DPkNLes8.js +1 -0
  17. package/dist/frontend/assets/bicep-Corcdgou.js +2 -0
  18. package/dist/frontend/assets/cameligo-CGrWLZr3.js +1 -0
  19. package/dist/frontend/assets/clojure-D9WOWImG.js +1 -0
  20. package/dist/frontend/assets/codicon-DCmgc-ay.ttf +0 -0
  21. package/dist/frontend/assets/coffee-B7EJu28W.js +1 -0
  22. package/dist/frontend/assets/cpp-SEyurbux.js +1 -0
  23. package/dist/frontend/assets/csharp-BoL64M5l.js +1 -0
  24. package/dist/frontend/assets/csp-C46ZqvIl.js +1 -0
  25. package/dist/frontend/assets/css-DQU6DXDx.js +3 -0
  26. package/dist/frontend/assets/cssMode-BDT3WbVs.js +4 -0
  27. package/dist/frontend/assets/cypher-D84EuPTj.js +1 -0
  28. package/dist/frontend/assets/dart-D8lhlL1r.js +1 -0
  29. package/dist/frontend/assets/dockerfile-DLk6rpji.js +1 -0
  30. package/dist/frontend/assets/ecl-BO6FnfXk.js +1 -0
  31. package/dist/frontend/assets/editor.worker-B4pQIWZD.js +12 -0
  32. package/dist/frontend/assets/elixir-BRjLKONM.js +1 -0
  33. package/dist/frontend/assets/flow9-Cac8vKd7.js +1 -0
  34. package/dist/frontend/assets/freemarker2-C7-hEgID.js +3 -0
  35. package/dist/frontend/assets/fsharp-fd1GTHhf.js +1 -0
  36. package/dist/frontend/assets/go-O9LJTZXk.js +1 -0
  37. package/dist/frontend/assets/graphql-LQdxqEYJ.js +1 -0
  38. package/dist/frontend/assets/handlebars-4cwTkPir.js +1 -0
  39. package/dist/frontend/assets/hcl-DxDQ3s82.js +1 -0
  40. package/dist/frontend/assets/html-YNfE1Q0A.js +1 -0
  41. package/dist/frontend/assets/htmlMode-opTQ1HoB.js +4 -0
  42. package/dist/frontend/assets/index-DWyaVa1h.js +782 -0
  43. package/dist/frontend/assets/index-lXrsgeTF.css +1 -0
  44. package/dist/frontend/assets/ini-BvajGCUy.js +1 -0
  45. package/dist/frontend/assets/java-SYsfObOQ.js +1 -0
  46. package/dist/frontend/assets/javascript-BEwGzk7T.js +1 -0
  47. package/dist/frontend/assets/jsonMode-CGhIS5Al.js +10 -0
  48. package/dist/frontend/assets/julia-DQXNmw_w.js +1 -0
  49. package/dist/frontend/assets/kotlin-qQ0MG-9I.js +1 -0
  50. package/dist/frontend/assets/less-GGFNNJHn.js +2 -0
  51. package/dist/frontend/assets/lexon-Canl7DCW.js +1 -0
  52. package/dist/frontend/assets/liquid-QekTGCGJ.js +1 -0
  53. package/dist/frontend/assets/lua-D28Ae8-K.js +1 -0
  54. package/dist/frontend/assets/m3-DPitgjJI.js +1 -0
  55. package/dist/frontend/assets/markdown-B811l8j2.js +1 -0
  56. package/dist/frontend/assets/mdx-BAVDaB7v.js +1 -0
  57. package/dist/frontend/assets/mips-CdjsipkG.js +1 -0
  58. package/dist/frontend/assets/msdax-CYqgjx_P.js +1 -0
  59. package/dist/frontend/assets/mysql-BHd6q0vd.js +1 -0
  60. package/dist/frontend/assets/objective-c-B1aVtJYH.js +1 -0
  61. package/dist/frontend/assets/pascal-BhNW15KB.js +1 -0
  62. package/dist/frontend/assets/pascaligo-5jv8CcQD.js +1 -0
  63. package/dist/frontend/assets/perl-DlYyT36c.js +1 -0
  64. package/dist/frontend/assets/pgsql-Dy0bjov7.js +1 -0
  65. package/dist/frontend/assets/php-120yhfDK.js +1 -0
  66. package/dist/frontend/assets/pla-CjnFlu4u.js +1 -0
  67. package/dist/frontend/assets/postiats-CQpG440k.js +1 -0
  68. package/dist/frontend/assets/powerquery-DdJtto1Z.js +1 -0
  69. package/dist/frontend/assets/powershell-Bu_VLpJB.js +1 -0
  70. package/dist/frontend/assets/protobuf-IBS6jZEB.js +2 -0
  71. package/dist/frontend/assets/pug-kFxLfcjb.js +1 -0
  72. package/dist/frontend/assets/python-BQlHw7XO.js +1 -0
  73. package/dist/frontend/assets/qsharp-q7JyzKFN.js +1 -0
  74. package/dist/frontend/assets/r-BIFz-_sK.js +1 -0
  75. package/dist/frontend/assets/razor-Be3Wwc2E.js +1 -0
  76. package/dist/frontend/assets/redis-CHOsPHWR.js +1 -0
  77. package/dist/frontend/assets/redshift-CBifECDb.js +1 -0
  78. package/dist/frontend/assets/restructuredtext-CghPJEOS.js +1 -0
  79. package/dist/frontend/assets/ruby-CYWGW-b1.js +1 -0
  80. package/dist/frontend/assets/rust-DMDD0SHb.js +1 -0
  81. package/dist/frontend/assets/sb-BYAiYHFx.js +1 -0
  82. package/dist/frontend/assets/scala-Bqvq8jcR.js +1 -0
  83. package/dist/frontend/assets/scheme-Dhb-2j9p.js +1 -0
  84. package/dist/frontend/assets/scss-CTwUZ5N7.js +3 -0
  85. package/dist/frontend/assets/shell-CsDZo4DB.js +1 -0
  86. package/dist/frontend/assets/solidity-CME5AdoB.js +1 -0
  87. package/dist/frontend/assets/sophia-RYC1BQQz.js +1 -0
  88. package/dist/frontend/assets/sparql-KEyrF7De.js +1 -0
  89. package/dist/frontend/assets/sql-BdTr02Mf.js +1 -0
  90. package/dist/frontend/assets/st-C7iG7M4S.js +1 -0
  91. package/dist/frontend/assets/swift-D7IUmUK8.js +1 -0
  92. package/dist/frontend/assets/systemverilog-DgMryOEJ.js +1 -0
  93. package/dist/frontend/assets/tcl-PloMZuKG.js +1 -0
  94. package/dist/frontend/assets/tsMode-CIBFoN3z.js +11 -0
  95. package/dist/frontend/assets/twig-BfRIq3la.js +1 -0
  96. package/dist/frontend/assets/typescript-BuV9wEIE.js +1 -0
  97. package/dist/frontend/assets/typespec-CzxlYoT_.js +1 -0
  98. package/dist/frontend/assets/vb-BwAE3J76.js +1 -0
  99. package/dist/frontend/assets/wgsl-B_1kOXbF.js +298 -0
  100. package/dist/frontend/assets/xml-DcDKYaM4.js +1 -0
  101. package/dist/frontend/assets/yaml-CuBNmOuI.js +1 -0
  102. package/dist/frontend/index.html +14 -0
  103. package/dist/frontend/logo.svg +50 -0
  104. package/dist/index.js +169 -0
  105. package/dist/logging.js +30 -0
  106. package/dist/opencode-auth/index.js +122 -0
  107. package/dist/opencode-server.js +91 -0
  108. package/dist/prisma/client.js +38 -0
  109. package/dist/reset-password.js +73 -0
  110. package/dist/rotate-jwt-secret.js +20 -0
  111. package/dist/scripts/prisma-workspace.js +34 -0
  112. package/dist/skills/index.js +311 -0
  113. package/dist/types/permissions.js +2 -0
  114. package/dist/types/shared/permissions.js +17 -0
  115. package/dist/types/shared/skill.js +17 -0
  116. package/dist/types/shared/workflow-files.js +17 -0
  117. package/dist/types/shared/workflow.js +17 -0
  118. package/dist/types/skill.js +2 -0
  119. package/dist/types/workflow-files.js +2 -0
  120. package/dist/types/workflow.js +2 -0
  121. package/dist/users/index.js +22 -0
  122. package/dist/utils/approval-snapshot-common.js +596 -0
  123. package/dist/utils/assert.js +20 -0
  124. package/dist/utils/chat-session.js +44 -0
  125. package/dist/utils/cli-bootstrap.js +26 -0
  126. package/dist/utils/index.js +95 -0
  127. package/dist/utils/jwt-secret.js +63 -0
  128. package/dist/utils/opencode-auth.js +126 -0
  129. package/dist/utils/opencode-client.js +109 -0
  130. package/dist/utils/password-policy.js +12 -0
  131. package/dist/utils/permission-common.js +280 -0
  132. package/dist/utils/redact.js +108 -0
  133. package/dist/utils/resource-access.js +37 -0
  134. package/dist/utils/resource-file-routes.js +115 -0
  135. package/dist/utils/resource-files.js +572 -0
  136. package/dist/utils/runtime-paths.js +61 -0
  137. package/dist/utils/session-abort.js +52 -0
  138. package/dist/utils/skill-approval-snapshot.js +39 -0
  139. package/dist/utils/skill-files.js +17 -0
  140. package/dist/utils/skill-permissions.js +15 -0
  141. package/dist/utils/skill.js +217 -0
  142. package/dist/utils/user-role.js +14 -0
  143. package/dist/utils/workflow-approval-snapshot.js +38 -0
  144. package/dist/utils/workflow-files.js +17 -0
  145. package/dist/utils/workflow-interruption.js +50 -0
  146. package/dist/utils/workflow-permissions.js +27 -0
  147. package/dist/utils/workflow-runner.js +414 -0
  148. package/dist/utils/workflow.js +158 -0
  149. package/dist/utils/workspace-sync.js +204 -0
  150. package/dist/workflows/index.js +751 -0
  151. package/dist/workspace_files/.opencode/opencode.json +17 -0
  152. package/dist/workspace_files/.opencode/package.json +14 -0
  153. package/dist/workspace_files/.opencode/plugins/createSkill.ts +339 -0
  154. package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +345 -0
  155. package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +173 -0
  156. package/dist/workspace_files/.opencode/plugins/findSkill.ts +211 -0
  157. package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +135 -0
  158. package/dist/workspace_files/.opencode/plugins/honeytoken-protection.ts +64 -0
  159. package/dist/workspace_files/.opencode/plugins/listAvailableSkills.ts +93 -0
  160. package/dist/workspace_files/.opencode/plugins/listAvailableWorkflows.ts +93 -0
  161. package/dist/workspace_files/.opencode/plugins/python-protection.ts +184 -0
  162. package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +168 -0
  163. package/dist/workspace_files/.opencode/tsconfig.json +16 -0
  164. package/dist/workspace_files/AGENTS.md +483 -0
  165. package/dist/workspace_files/package-lock.json +167 -0
  166. package/dist/workspace_files/package.json +5 -0
  167. package/package.json +86 -0
  168. package/prisma/migrations/20260203040755_init/migration.sql +20 -0
  169. package/prisma/migrations/20260204034845_replace_google_auth_with_email_password/migration.sql +25 -0
  170. package/prisma/migrations/20260207022226_add_user_role/migration.sql +25 -0
  171. package/prisma/migrations/20260210161254_add_workflow_runs/migration.sql +16 -0
  172. package/prisma/migrations/20260211050606_adds_workflow_table/migration.sql +40 -0
  173. package/prisma/migrations/20260211050750_adds_fkey_constraint/migration.sql +21 -0
  174. package/prisma/migrations/20260211051912_removes_workflow_table/migration.sql +34 -0
  175. package/prisma/migrations/20260211052238_changes_workflow_id_to_slug/migration.sql +27 -0
  176. package/prisma/migrations/20260212051912_add_output_to_workflow_runs/migration.sql +2 -0
  177. package/prisma/migrations/20260213073006_add_chat_sessions/migration.sql +13 -0
  178. package/prisma/migrations/20260216053202_add_chat_sessions_opencode_session_id_idx/migration.sql +2 -0
  179. package/prisma/migrations/20260216053237_drop_redundant_chat_sessions_opencode_idx/migration.sql +2 -0
  180. package/prisma/migrations/20260219060705_makes/migration.sql +24 -0
  181. package/prisma/migrations/20260222040542_add_workflow_execution_permissions/migration.sql +18 -0
  182. package/prisma/migrations/20260222040815_remove_workflow_execution_permissions/migration.sql +10 -0
  183. package/prisma/migrations/20260222041348_add_workflow_execution_permissions_final/migration.sql +17 -0
  184. package/prisma/migrations/20260222041741_rename_to_tool_execution_permissions/migration.sql +30 -0
  185. package/prisma/migrations/20260222041826_simplify_tool_execution_permissions/migration.sql +29 -0
  186. package/prisma/migrations/20260222041950_add_fields_for_standalone_permissions/migration.sql +32 -0
  187. package/prisma/migrations/20260222042954_simplify_tool_permissions_table/migration.sql +27 -0
  188. package/prisma/migrations/20260223073902_add_workflow_run_permissions_tables/migration.sql +23 -0
  189. package/prisma/migrations/20260225025151_add_workflow_metadata/migration.sql +16 -0
  190. package/prisma/migrations/20260225031035_merge_workflow_permissions_into_metadata/migration.sql +44 -0
  191. package/prisma/migrations/20260225031752_removes_default_for_run_permission_mode/migration.sql +20 -0
  192. package/prisma/migrations/20260225033603_remove_workflow_metadata_user_fkeys/migration.sql +18 -0
  193. package/prisma/migrations/20260225043032_restore_workflow_metadata_user_fkeys/migration.sql +20 -0
  194. package/prisma/migrations/20260225091423_add_workflow_approved_snapshots/migration.sql +28 -0
  195. package/prisma/migrations/20260226032121_add_is_approved_to_workflow_metadata/migration.sql +21 -0
  196. package/prisma/migrations/20260226032444_undoes_last_db_change/migration.sql +26 -0
  197. package/prisma/migrations/20260227120000_remove_snapshot_hash_from_approved_snapshots/migration.sql +16 -0
  198. package/prisma/migrations/20260228071125_adds_workspace_path_to_snapshot_table/migration.sql +22 -0
  199. package/prisma/migrations/20260228071217_modifies_index_and_removes_default_value/migration.sql +22 -0
  200. package/prisma/migrations/20260228071710_undoes_previous/migration.sql +27 -0
  201. package/prisma/migrations/20260228105022_add_must_change_password_first_login/migration.sql +20 -0
  202. package/prisma/migrations/20260301115439_add_workflow_run_log_refs/migration.sql +8 -0
  203. package/prisma/migrations/20260301122557_add_workflow_aborted_sessions/migration.sql +5 -0
  204. package/prisma/migrations/20260302045545_move_workflow_run_log_refs_into_workflow_runs/migration.sql +17 -0
  205. package/prisma/migrations/20260303040318_add_skill_tables/migration.sql +61 -0
  206. package/prisma/migrations/20260303051533_unify_resource_permissions/migration.sql +97 -0
  207. package/prisma/migrations/20260303064255_unify_resource_metadata_and_snapshots/migration.sql +179 -0
  208. package/prisma/migrations/migration_lock.toml +3 -0
  209. package/prisma/schema.prisma +147 -0
@@ -0,0 +1,596 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.collectCurrentResourceSnapshot = collectCurrentResourceSnapshot;
7
+ exports.loadApprovedSnapshotFromDb = loadApprovedSnapshotFromDb;
8
+ exports.approveResourceWithSnapshot = approveResourceWithSnapshot;
9
+ exports.restoreResourceToApprovedSnapshot = restoreResourceToApprovedSnapshot;
10
+ exports.getResourceSnapshotApprovalState = getResourceSnapshotApprovalState;
11
+ exports.buildApprovalDiffResponse = buildApprovalDiffResponse;
12
+ const crypto_1 = __importDefault(require("crypto"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const client_1 = __importDefault(require("../prisma/client"));
16
+ const MAX_INLINE_DIFF_FILE_BYTES = 200 * 1024;
17
+ const MAX_PATCH_LINES_PER_FILE = 5000;
18
+ function capitalize(value) {
19
+ if (!value) {
20
+ return value;
21
+ }
22
+ return value[0].toUpperCase() + value.slice(1);
23
+ }
24
+ function sha256(buffer) {
25
+ return crypto_1.default.createHash("sha256").update(buffer).digest("hex");
26
+ }
27
+ function looksBinary(buffer) {
28
+ if (buffer.length === 0)
29
+ return false;
30
+ const sample = buffer.subarray(0, Math.min(buffer.length, 8000));
31
+ for (let i = 0; i < sample.length; i += 1) {
32
+ if (sample[i] === 0) {
33
+ return true;
34
+ }
35
+ }
36
+ return false;
37
+ }
38
+ function shouldIgnoreRelativePath(relativePath) {
39
+ if (!relativePath) {
40
+ return false;
41
+ }
42
+ const segments = relativePath.split("/").filter(Boolean);
43
+ for (const segment of segments) {
44
+ if (segment.startsWith(".")) {
45
+ return true;
46
+ }
47
+ if (segment === "__pycache__") {
48
+ return true;
49
+ }
50
+ if (segment === "data") {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ function assertNoSymlink(absolutePath, resourceLabel) {
57
+ const lstat = fs_1.default.lstatSync(absolutePath);
58
+ if (lstat.isSymbolicLink()) {
59
+ throw {
60
+ status: 400,
61
+ message: `${capitalize(resourceLabel)} approval snapshot does not support symlinks`
62
+ };
63
+ }
64
+ }
65
+ function ensureDirectoryExists(absoluteDir) {
66
+ fs_1.default.mkdirSync(absoluteDir, { recursive: true });
67
+ }
68
+ function collectFilesRecursive(absoluteDir, relativeDir, out, resourceLabel) {
69
+ const entries = fs_1.default.readdirSync(absoluteDir, { withFileTypes: true })
70
+ .sort((a, b) => a.name.localeCompare(b.name));
71
+ for (const entry of entries) {
72
+ const rel = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
73
+ if (shouldIgnoreRelativePath(rel)) {
74
+ continue;
75
+ }
76
+ const abs = path_1.default.join(absoluteDir, entry.name);
77
+ assertNoSymlink(abs, resourceLabel);
78
+ if (entry.isDirectory()) {
79
+ collectFilesRecursive(abs, rel, out, resourceLabel);
80
+ continue;
81
+ }
82
+ if (!entry.isFile()) {
83
+ continue;
84
+ }
85
+ const bytes = fs_1.default.readFileSync(abs);
86
+ const binary = looksBinary(bytes);
87
+ out.push({
88
+ relative_path: rel,
89
+ content_kind: binary ? "binary" : "text",
90
+ text_content: binary ? null : bytes.toString("utf-8"),
91
+ binary_content: binary ? bytes : null,
92
+ size_bytes: bytes.length,
93
+ content_sha256: sha256(bytes),
94
+ });
95
+ }
96
+ }
97
+ function collectCurrentNonIgnoredFilePaths(absoluteDir, relativeDir, out, resourceLabel) {
98
+ const entries = fs_1.default.readdirSync(absoluteDir, { withFileTypes: true })
99
+ .sort((a, b) => a.name.localeCompare(b.name));
100
+ for (const entry of entries) {
101
+ const rel = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
102
+ if (shouldIgnoreRelativePath(rel)) {
103
+ continue;
104
+ }
105
+ const abs = path_1.default.join(absoluteDir, entry.name);
106
+ assertNoSymlink(abs, resourceLabel);
107
+ if (entry.isDirectory()) {
108
+ collectCurrentNonIgnoredFilePaths(abs, rel, out, resourceLabel);
109
+ continue;
110
+ }
111
+ if (entry.isFile()) {
112
+ out.push(rel);
113
+ }
114
+ }
115
+ }
116
+ function pruneEmptyNonIgnoredDirectories(rootPath, absoluteDir, relativeDir, resourceLabel) {
117
+ const entries = fs_1.default.readdirSync(absoluteDir, { withFileTypes: true });
118
+ for (const entry of entries) {
119
+ const rel = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
120
+ if (shouldIgnoreRelativePath(rel)) {
121
+ continue;
122
+ }
123
+ const abs = path_1.default.join(absoluteDir, entry.name);
124
+ assertNoSymlink(abs, resourceLabel);
125
+ if (entry.isDirectory()) {
126
+ pruneEmptyNonIgnoredDirectories(rootPath, abs, rel, resourceLabel);
127
+ }
128
+ }
129
+ if (absoluteDir === rootPath) {
130
+ return;
131
+ }
132
+ const remaining = fs_1.default.readdirSync(absoluteDir, { withFileTypes: true })
133
+ .filter((entry) => !shouldIgnoreRelativePath(relativeDir ? `${relativeDir}/${entry.name}` : entry.name));
134
+ if (remaining.length === 0) {
135
+ fs_1.default.rmdirSync(absoluteDir);
136
+ }
137
+ }
138
+ function computeSnapshotHash(files) {
139
+ const hash = crypto_1.default.createHash("sha256");
140
+ const sorted = files
141
+ .sort((a, b) => a.relative_path.localeCompare(b.relative_path));
142
+ for (const file of sorted) {
143
+ hash.update(file.relative_path);
144
+ hash.update("\0");
145
+ hash.update(file.content_kind);
146
+ hash.update("\0");
147
+ hash.update(file.content_sha256);
148
+ hash.update("\n");
149
+ }
150
+ return hash.digest("hex");
151
+ }
152
+ function collectCurrentResourceSnapshot(options) {
153
+ const { root_path: rootPath, slug, resource_label: resourceLabel } = options;
154
+ if (!fs_1.default.existsSync(rootPath)) {
155
+ throw {
156
+ status: 404,
157
+ message: `${capitalize(resourceLabel)} not found for slug: ${slug}`
158
+ };
159
+ }
160
+ assertNoSymlink(rootPath, resourceLabel);
161
+ if (!fs_1.default.statSync(rootPath).isDirectory()) {
162
+ throw {
163
+ status: 400,
164
+ message: `${capitalize(resourceLabel)} path is not a directory`
165
+ };
166
+ }
167
+ const files = [];
168
+ collectFilesRecursive(rootPath, "", files, resourceLabel);
169
+ files.sort((a, b) => a.relative_path.localeCompare(b.relative_path));
170
+ return {
171
+ workflow_slug: slug,
172
+ files,
173
+ file_count: files.length,
174
+ snapshot_hash: computeSnapshotHash(files),
175
+ };
176
+ }
177
+ async function writeApprovedSnapshotInTx(tx, resourceKind, slug, snapshot, now) {
178
+ const existingSnapshot = await tx.resource_approved_snapshots.findUnique({
179
+ where: {
180
+ resource_kind_resource_slug: {
181
+ resource_kind: resourceKind,
182
+ resource_slug: slug
183
+ }
184
+ },
185
+ select: { resource_slug: true }
186
+ });
187
+ if (existingSnapshot) {
188
+ await tx.resource_approved_snapshot_files.deleteMany({
189
+ where: {
190
+ resource_kind: resourceKind,
191
+ resource_slug: slug
192
+ }
193
+ });
194
+ await tx.resource_approved_snapshots.update({
195
+ where: {
196
+ resource_kind_resource_slug: {
197
+ resource_kind: resourceKind,
198
+ resource_slug: slug
199
+ }
200
+ },
201
+ data: {
202
+ file_count: snapshot.file_count,
203
+ updated_at: now,
204
+ }
205
+ });
206
+ }
207
+ else {
208
+ await tx.resource_approved_snapshots.create({
209
+ data: {
210
+ resource_kind: resourceKind,
211
+ resource_slug: slug,
212
+ file_count: snapshot.file_count,
213
+ created_at: now,
214
+ updated_at: now,
215
+ }
216
+ });
217
+ }
218
+ if (snapshot.files.length > 0) {
219
+ await tx.resource_approved_snapshot_files.createMany({
220
+ data: snapshot.files.map((file) => ({
221
+ resource_kind: resourceKind,
222
+ resource_slug: slug,
223
+ relative_path: file.relative_path,
224
+ content_kind: file.content_kind,
225
+ text_content: file.text_content,
226
+ binary_content: file.binary_content ? new Uint8Array(file.binary_content) : null,
227
+ size_bytes: file.size_bytes,
228
+ content_sha256: file.content_sha256,
229
+ }))
230
+ });
231
+ }
232
+ }
233
+ async function loadApprovedSnapshotFromDb(resourceKind, slug) {
234
+ const row = await client_1.default.resource_approved_snapshots.findUnique({
235
+ where: {
236
+ resource_kind_resource_slug: {
237
+ resource_kind: resourceKind,
238
+ resource_slug: slug
239
+ }
240
+ },
241
+ include: { files: true }
242
+ });
243
+ if (!row) {
244
+ return null;
245
+ }
246
+ const files = row.files
247
+ .filter((file) => !shouldIgnoreRelativePath(file.relative_path))
248
+ .map((file) => ({
249
+ relative_path: file.relative_path,
250
+ content_kind: (file.content_kind === "binary" ? "binary" : "text"),
251
+ text_content: file.text_content,
252
+ binary_content: file.binary_content ? Buffer.from(file.binary_content) : null,
253
+ size_bytes: file.size_bytes,
254
+ content_sha256: file.content_sha256,
255
+ }))
256
+ .sort((a, b) => a.relative_path.localeCompare(b.relative_path));
257
+ return {
258
+ workflow_slug: slug,
259
+ snapshot_hash: computeSnapshotHash(files),
260
+ file_count: files.length,
261
+ files,
262
+ };
263
+ }
264
+ async function approveResourceWithSnapshot(config, userId) {
265
+ const user = await client_1.default.users.findUnique({ where: { id: userId } });
266
+ if (!user) {
267
+ throw {
268
+ status: 404,
269
+ message: "User not found"
270
+ };
271
+ }
272
+ if (user.role !== "Engineer") {
273
+ throw {
274
+ status: 403,
275
+ message: `Only Engineers can approve ${config.resource_label}s`
276
+ };
277
+ }
278
+ await config.ensure_resource_exists();
279
+ const snapshot = collectCurrentResourceSnapshot(config);
280
+ const now = Date.now();
281
+ await client_1.default.$transaction(async (tx) => {
282
+ const nowBigInt = BigInt(now);
283
+ await writeApprovedSnapshotInTx(tx, config.resource_kind, config.slug, snapshot, nowBigInt);
284
+ await tx.resource_metadata.update({
285
+ where: {
286
+ resource_kind_resource_slug: {
287
+ resource_kind: config.resource_kind,
288
+ resource_slug: config.slug
289
+ }
290
+ },
291
+ data: {
292
+ approved_by_user_id: userId,
293
+ updated_at: nowBigInt,
294
+ }
295
+ });
296
+ });
297
+ return {
298
+ approved_by_user_id: userId,
299
+ snapshot_hash: snapshot.snapshot_hash,
300
+ snapshot_file_count: snapshot.file_count,
301
+ };
302
+ }
303
+ async function restoreResourceToApprovedSnapshot(config, userId) {
304
+ const user = await client_1.default.users.findUnique({ where: { id: userId } });
305
+ if (!user) {
306
+ throw {
307
+ status: 404,
308
+ message: "User not found"
309
+ };
310
+ }
311
+ if (user.role !== "Engineer") {
312
+ throw {
313
+ status: 403,
314
+ message: `Only Engineers can restore ${config.resource_label}s from approved snapshots`
315
+ };
316
+ }
317
+ await config.ensure_resource_exists();
318
+ const snapshot = await loadApprovedSnapshotFromDb(config.resource_kind, config.slug);
319
+ if (!snapshot) {
320
+ throw {
321
+ status: 409,
322
+ message: "No approved snapshot exists to restore"
323
+ };
324
+ }
325
+ const rootPath = config.root_path;
326
+ assertNoSymlink(rootPath, config.resource_label);
327
+ const currentPaths = [];
328
+ collectCurrentNonIgnoredFilePaths(rootPath, "", currentPaths, config.resource_label);
329
+ const snapshotPathSet = new Set(snapshot.files.map((file) => file.relative_path));
330
+ for (const currentPath of currentPaths) {
331
+ if (snapshotPathSet.has(currentPath)) {
332
+ continue;
333
+ }
334
+ const absolutePath = path_1.default.join(rootPath, currentPath);
335
+ if (fs_1.default.existsSync(absolutePath) && fs_1.default.statSync(absolutePath).isFile()) {
336
+ fs_1.default.unlinkSync(absolutePath);
337
+ }
338
+ }
339
+ for (const file of snapshot.files) {
340
+ const absolutePath = path_1.default.join(rootPath, file.relative_path);
341
+ ensureDirectoryExists(path_1.default.dirname(absolutePath));
342
+ const bytes = file.content_kind === "binary"
343
+ ? (file.binary_content ?? Buffer.alloc(0))
344
+ : Buffer.from(file.text_content ?? "", "utf-8");
345
+ fs_1.default.writeFileSync(absolutePath, bytes);
346
+ }
347
+ pruneEmptyNonIgnoredDirectories(rootPath, rootPath, "", config.resource_label);
348
+ return {
349
+ restored_file_count: snapshot.file_count,
350
+ snapshot_hash: computeSnapshotHash(snapshot.files),
351
+ };
352
+ }
353
+ async function getResourceSnapshotApprovalState(config) {
354
+ const approvedSnapshot = await loadApprovedSnapshotFromDb(config.resource_kind, config.slug);
355
+ if (!approvedSnapshot) {
356
+ return {
357
+ has_approved_snapshot: false,
358
+ is_current_code_approved: false,
359
+ };
360
+ }
361
+ const currentSnapshot = collectCurrentResourceSnapshot(config);
362
+ const approvedSnapshotHash = computeSnapshotHash(approvedSnapshot.files);
363
+ const matches = approvedSnapshotHash === currentSnapshot.snapshot_hash;
364
+ return {
365
+ has_approved_snapshot: true,
366
+ is_current_code_approved: matches,
367
+ };
368
+ }
369
+ function splitLines(input) {
370
+ return input.split(/\r?\n/);
371
+ }
372
+ function diffLinesSimple(oldText, newText) {
373
+ const a = splitLines(oldText);
374
+ const b = splitLines(newText);
375
+ const dp = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
376
+ for (let i = a.length - 1; i >= 0; i -= 1) {
377
+ for (let j = b.length - 1; j >= 0; j -= 1) {
378
+ dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
379
+ }
380
+ }
381
+ const ops = [];
382
+ let i = 0;
383
+ let j = 0;
384
+ while (i < a.length && j < b.length) {
385
+ if (a[i] === b[j]) {
386
+ ops.push({ kind: "context", line: a[i] });
387
+ i += 1;
388
+ j += 1;
389
+ continue;
390
+ }
391
+ if (dp[i + 1][j] >= dp[i][j + 1]) {
392
+ ops.push({ kind: "remove", line: a[i] });
393
+ i += 1;
394
+ }
395
+ else {
396
+ ops.push({ kind: "add", line: b[j] });
397
+ j += 1;
398
+ }
399
+ }
400
+ while (i < a.length) {
401
+ ops.push({ kind: "remove", line: a[i] });
402
+ i += 1;
403
+ }
404
+ while (j < b.length) {
405
+ ops.push({ kind: "add", line: b[j] });
406
+ j += 1;
407
+ }
408
+ return ops;
409
+ }
410
+ function buildPatchLines(oldText, newText) {
411
+ const ops = diffLinesSimple(oldText, newText);
412
+ const lines = ["@@"];
413
+ for (const op of ops) {
414
+ if (op.kind === "context") {
415
+ lines.push(` ${op.line}`);
416
+ }
417
+ else if (op.kind === "add") {
418
+ lines.push(`+${op.line}`);
419
+ }
420
+ else {
421
+ lines.push(`-${op.line}`);
422
+ }
423
+ if (lines.length >= MAX_PATCH_LINES_PER_FILE) {
424
+ return { patch_lines: lines, is_truncated: true };
425
+ }
426
+ }
427
+ return { patch_lines: lines, is_truncated: false };
428
+ }
429
+ function toFileMap(snapshot) {
430
+ return new Map((snapshot?.files ?? []).map((file) => [file.relative_path, file]));
431
+ }
432
+ function diffFileToResponse(pathKey, previousFile, currentFile) {
433
+ if (previousFile === null && currentFile !== null) {
434
+ if (currentFile.content_kind === "text") {
435
+ const canInline = currentFile.size_bytes <= MAX_INLINE_DIFF_FILE_BYTES && currentFile.text_content !== null;
436
+ if (canInline) {
437
+ const currentText = currentFile.text_content;
438
+ const patch = buildPatchLines("", currentText);
439
+ return {
440
+ path: pathKey,
441
+ status: "added",
442
+ kind: "text",
443
+ old_size_bytes: null,
444
+ new_size_bytes: currentFile.size_bytes,
445
+ patch_lines: patch.patch_lines,
446
+ is_truncated: patch.is_truncated,
447
+ message: null,
448
+ };
449
+ }
450
+ return {
451
+ path: pathKey,
452
+ status: "added",
453
+ kind: "text",
454
+ old_size_bytes: null,
455
+ new_size_bytes: currentFile.size_bytes,
456
+ patch_lines: null,
457
+ is_truncated: false,
458
+ message: "Diff omitted for large file",
459
+ };
460
+ }
461
+ return {
462
+ path: pathKey,
463
+ status: "added",
464
+ kind: "binary",
465
+ old_size_bytes: null,
466
+ new_size_bytes: currentFile.size_bytes,
467
+ patch_lines: null,
468
+ is_truncated: false,
469
+ message: "Binary file added",
470
+ };
471
+ }
472
+ if (previousFile !== null && currentFile === null) {
473
+ if (previousFile.content_kind === "text") {
474
+ const canInline = previousFile.size_bytes <= MAX_INLINE_DIFF_FILE_BYTES && previousFile.text_content !== null;
475
+ if (canInline) {
476
+ const previousText = previousFile.text_content;
477
+ const patch = buildPatchLines(previousText, "");
478
+ return {
479
+ path: pathKey,
480
+ status: "deleted",
481
+ kind: "text",
482
+ old_size_bytes: previousFile.size_bytes,
483
+ new_size_bytes: null,
484
+ patch_lines: patch.patch_lines,
485
+ is_truncated: patch.is_truncated,
486
+ message: null,
487
+ };
488
+ }
489
+ return {
490
+ path: pathKey,
491
+ status: "deleted",
492
+ kind: "text",
493
+ old_size_bytes: previousFile.size_bytes,
494
+ new_size_bytes: null,
495
+ patch_lines: null,
496
+ is_truncated: false,
497
+ message: "Diff omitted for large file",
498
+ };
499
+ }
500
+ return {
501
+ path: pathKey,
502
+ status: "deleted",
503
+ kind: "binary",
504
+ old_size_bytes: previousFile.size_bytes,
505
+ new_size_bytes: null,
506
+ patch_lines: null,
507
+ is_truncated: false,
508
+ message: "Binary file deleted",
509
+ };
510
+ }
511
+ const before = previousFile;
512
+ const after = currentFile;
513
+ if (before.content_kind === "text" && after.content_kind === "text") {
514
+ const canInline = before.size_bytes <= MAX_INLINE_DIFF_FILE_BYTES
515
+ && after.size_bytes <= MAX_INLINE_DIFF_FILE_BYTES
516
+ && before.text_content !== null
517
+ && after.text_content !== null;
518
+ if (canInline) {
519
+ const beforeText = before.text_content;
520
+ const afterText = after.text_content;
521
+ const patch = buildPatchLines(beforeText, afterText);
522
+ return {
523
+ path: pathKey,
524
+ status: "modified",
525
+ kind: "text",
526
+ old_size_bytes: before.size_bytes,
527
+ new_size_bytes: after.size_bytes,
528
+ patch_lines: patch.patch_lines,
529
+ is_truncated: patch.is_truncated,
530
+ message: null,
531
+ };
532
+ }
533
+ return {
534
+ path: pathKey,
535
+ status: "modified",
536
+ kind: "text",
537
+ old_size_bytes: before.size_bytes,
538
+ new_size_bytes: after.size_bytes,
539
+ patch_lines: null,
540
+ is_truncated: false,
541
+ message: "Diff omitted for large file",
542
+ };
543
+ }
544
+ return {
545
+ path: pathKey,
546
+ status: "modified",
547
+ kind: "binary",
548
+ old_size_bytes: before.size_bytes,
549
+ new_size_bytes: after.size_bytes,
550
+ patch_lines: null,
551
+ is_truncated: false,
552
+ message: before.content_kind !== after.content_kind ? "File type changed" : "Binary file modified",
553
+ };
554
+ }
555
+ function buildApprovalDiffResponse(previous, current) {
556
+ const previousMap = toFileMap(previous);
557
+ const currentMap = toFileMap(current);
558
+ const allPaths = Array.from(new Set([...previousMap.keys(), ...currentMap.keys()])).sort((a, b) => a.localeCompare(b));
559
+ const files = [];
560
+ const summary = {
561
+ added: 0,
562
+ modified: 0,
563
+ deleted: 0,
564
+ text_files: 0,
565
+ binary_files: 0,
566
+ };
567
+ for (const pathKey of allPaths) {
568
+ const before = previousMap.get(pathKey) ?? null;
569
+ const after = currentMap.get(pathKey) ?? null;
570
+ if (before && after && before.content_sha256 === after.content_sha256 && before.content_kind === after.content_kind) {
571
+ continue;
572
+ }
573
+ const diffFile = diffFileToResponse(pathKey, before, after);
574
+ files.push(diffFile);
575
+ if (diffFile.status === "added")
576
+ summary.added += 1;
577
+ if (diffFile.status === "modified")
578
+ summary.modified += 1;
579
+ if (diffFile.status === "deleted")
580
+ summary.deleted += 1;
581
+ if (diffFile.kind === "text")
582
+ summary.text_files += 1;
583
+ else
584
+ summary.binary_files += 1;
585
+ }
586
+ return {
587
+ has_previous_snapshot: previous !== null,
588
+ summary,
589
+ files,
590
+ ignored_rules: [
591
+ 'Any hidden file or directory (path segment starting with ".")',
592
+ 'Any "__pycache__" directory',
593
+ 'Any "data" directory'
594
+ ],
595
+ };
596
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertCondition = assertCondition;
4
+ exports.assertEnv = assertEnv;
5
+ exports.parseIntStrict = parseIntStrict;
6
+ function assertCondition(condition, message) {
7
+ if (!condition) {
8
+ throw new Error(message);
9
+ }
10
+ }
11
+ function assertEnv(name) {
12
+ const value = process.env[name];
13
+ assertCondition(typeof value === "string" && value.length > 0, `${name} is not set`);
14
+ return value;
15
+ }
16
+ function parseIntStrict(raw, label) {
17
+ const parsed = Number.parseInt(raw, 10);
18
+ assertCondition(Number.isFinite(parsed), `${label} must be a valid integer`);
19
+ return parsed;
20
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSessionStatusTypeForSession = getSessionStatusTypeForSession;
4
+ exports.normalizeStaleRunningTools = normalizeStaleRunningTools;
5
+ const assert_1 = require("./assert");
6
+ function getSessionStatusTypeForSession(statusMap, sessionId) {
7
+ const status = statusMap[sessionId];
8
+ return status ? status.type : 'idle';
9
+ }
10
+ function normalizeStaleRunningTools(messages, sessionStatusType) {
11
+ const isSessionBusy = sessionStatusType === 'busy' || sessionStatusType === 'retry';
12
+ if (isSessionBusy) {
13
+ return messages;
14
+ }
15
+ const now = Date.now();
16
+ return messages.map((container) => {
17
+ const nextParts = container.parts.map((part) => {
18
+ if (part.type !== 'tool')
19
+ return part;
20
+ if (part.state.status !== 'running' && part.state.status !== 'pending')
21
+ return part;
22
+ (0, assert_1.assertCondition)(typeof part.state.time?.start === 'number', `Missing tool start time for part '${part.id}' in message '${part.messageID}'`);
23
+ const runningStart = part.state.time.start;
24
+ const normalizedState = {
25
+ status: 'error',
26
+ input: part.state.input,
27
+ error: 'Tool call interrupted',
28
+ metadata: part.state.metadata,
29
+ time: {
30
+ start: runningStart,
31
+ end: now
32
+ }
33
+ };
34
+ return {
35
+ ...part,
36
+ state: normalizedState
37
+ };
38
+ });
39
+ return {
40
+ info: container.info,
41
+ parts: nextParts
42
+ };
43
+ });
44
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bootstrapCliDatabaseAccess = bootstrapCliDatabaseAccess;
4
+ exports.bootstrapCliJwtAccess = bootstrapCliJwtAccess;
5
+ const workspace_sync_1 = require("./workspace-sync");
6
+ const jwt_secret_1 = require("./jwt-secret");
7
+ let didBootstrapCliDatabaseAccess = false;
8
+ let didBootstrapCliJwtAccess = false;
9
+ async function bootstrapCliDatabaseAccess() {
10
+ if (didBootstrapCliDatabaseAccess) {
11
+ return;
12
+ }
13
+ (0, workspace_sync_1.ensureWorkspaceDatabaseDirectory)();
14
+ if (!(0, workspace_sync_1.workspaceDatabaseExists)()) {
15
+ await (0, workspace_sync_1.ensureWorkspaceDatabase)();
16
+ }
17
+ didBootstrapCliDatabaseAccess = true;
18
+ }
19
+ async function bootstrapCliJwtAccess() {
20
+ if (didBootstrapCliJwtAccess) {
21
+ return;
22
+ }
23
+ await bootstrapCliDatabaseAccess();
24
+ await (0, jwt_secret_1.loadJwtSecret)();
25
+ didBootstrapCliJwtAccess = true;
26
+ }