clay-server 5.2.0-beta.4 → 5.2.0-beta.5
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/lib/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +17 -14
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +28 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +96 -0
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +73 -221
- package/lib/issues-service.js +29 -15
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +4 -3
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-users.js +20 -5
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +4 -2
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +13 -87
- package/lib/project-full-access.js +23 -2
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pending-message-queue.js +1 -0
- package/lib/project-product-control.js +87 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +63 -64
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-github.js +47 -12
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +64 -241
- package/lib/project-user-message.js +30 -1
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +95 -3
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +3 -0
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +1 -1
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +18 -3
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +1 -1
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/home-debate-planning.js +5 -5
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +65 -8
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +71 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +34 -6
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/style.css +2 -0
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +220 -21
- package/lib/sdk-message-processor.js +2 -0
- package/lib/sdk-response-recovery.js +155 -0
- package/lib/server-admin.js +63 -16
- package/lib/server-dm.js +1 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +7 -3
- package/lib/server-home-chat.js +4 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +3 -3
- package/lib/server-mates.js +25 -67
- package/lib/server-owner-ui.js +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server.js +99 -29
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-github.js +9 -2
- package/lib/session-visibility.js +5 -2
- package/lib/sessions.js +10 -0
- package/lib/terminal-manager.js +52 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/adapters/claude.js +47 -8
- package/lib/yoke/adapters/codex.js +52 -111
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/session-tool-transport.js +3 -0
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -67,6 +67,8 @@ try {
|
|
|
67
67
|
console.error("[daemon] ensureMultiUser threw (continuing):", e && e.message);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
config = require("./project-ownership").repair(config, usersModule.getAllUsers(), saveConfig, osUsersMod.resolveOsUserInfo);
|
|
71
|
+
|
|
70
72
|
if (projectLogContext.initializeProjectKnowledge(config)) {
|
|
71
73
|
saveConfig(config);
|
|
72
74
|
console.log("[daemon] Assigned stable knowledge identities to existing projects");
|
|
@@ -187,7 +189,14 @@ function registerProjectWorktrees(project, requestUser) {
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
|
|
192
|
+
var projectLifecycle = require("./daemon-project-lifecycle").attachProjectLifecycle({
|
|
193
|
+
getConfig: function () { return config; }, setConfig: function (next) { config = next; }, saveConfig: saveConfig,
|
|
194
|
+
getRelay: function () { return relay; }, resolveIdentity: osUsersMod.resolveOsUserInfo,
|
|
195
|
+
findUser: usersModule.findUserById, registerWorktrees: registerProjectWorktrees,
|
|
196
|
+
});
|
|
197
|
+
var lifecycleCallbacks = require("./daemon-project-lifecycle-callbacks").attachLifecycleCallbacks({ service: projectLifecycle });
|
|
190
198
|
var relay = createServer({
|
|
199
|
+
projectLifecycle: projectLifecycle,
|
|
191
200
|
tlsOptions: tlsOptions,
|
|
192
201
|
caPath: caRoot,
|
|
193
202
|
builtinCert: config.builtinCert || false,
|
|
@@ -198,122 +207,11 @@ var relay = createServer({
|
|
|
198
207
|
osUsers: config.osUsers || false,
|
|
199
208
|
lanHost: lanIp ? lanIp + ":" + config.port : null,
|
|
200
209
|
getRemovedProjects: function (userId) { return getFilteredRemovedProjects(config, userId); },
|
|
201
|
-
onAddProject:
|
|
202
|
-
|
|
203
|
-
for (var j = 0; j < config.projects.length; j++) {
|
|
204
|
-
if (config.projects[j].path === absPath) {
|
|
205
|
-
return { ok: true, slug: config.projects[j].slug, existing: true };
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
var slugs = config.projects.map(function (p) { return p.slug; });
|
|
209
|
-
var slug = generateSlug(absPath, slugs);
|
|
210
|
-
var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
211
|
-
projectEntry.projectKnowledgeId = rememberedProjectKnowledgeId(absPath);
|
|
212
|
-
// The user who adds a project always becomes the owner
|
|
213
|
-
if (wsUser && wsUser.id) {
|
|
214
|
-
projectEntry.ownerId = wsUser.id;
|
|
215
|
-
}
|
|
216
|
-
config.projects.push(projectEntry);
|
|
217
|
-
relay.addProject(absPath, slug, null, null, wsUser ? wsUser.id : null, null, projectRuntimeOptions(projectEntry));
|
|
218
|
-
// Remove from removedProjects if present
|
|
219
|
-
if (config.removedProjects) {
|
|
220
|
-
config.removedProjects = config.removedProjects.filter(function (rp) { return rp.path !== absPath; });
|
|
221
|
-
}
|
|
222
|
-
saveConfig(config);
|
|
223
|
-
try { syncClayrc(config.projects); } catch (e) {}
|
|
224
|
-
console.log("[daemon] Added project (web):", slug, "→", absPath);
|
|
225
|
-
// OS users mode: grant ACL to project owner
|
|
226
|
-
if (config.osUsers) {
|
|
227
|
-
var newProj = config.projects[config.projects.length - 1];
|
|
228
|
-
if (newProj.ownerId) {
|
|
229
|
-
var ownerUser = usersModule.findUserById(newProj.ownerId);
|
|
230
|
-
if (ownerUser && ownerUser.linuxUser) {
|
|
231
|
-
grantProjectAccess(absPath, ownerUser.linuxUser);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Discover and register worktrees for the new project
|
|
236
|
-
registerProjectWorktrees(projectEntry, wsUser);
|
|
237
|
-
// Broadcast updated project list to all clients
|
|
238
|
-
relay.broadcastAll({
|
|
239
|
-
type: "projects_updated",
|
|
240
|
-
projects: relay.getProjects(),
|
|
241
|
-
projectCount: config.projects.length,
|
|
242
|
-
});
|
|
243
|
-
return { ok: true, slug: slug };
|
|
244
|
-
},
|
|
245
|
-
onCreateProject: function (projectName, wsUser) {
|
|
246
|
-
console.log("[daemon] onCreateProject wsUser:", JSON.stringify(wsUser ? { id: wsUser.id, role: wsUser.role, username: wsUser.username, linuxUser: wsUser.linuxUser } : null));
|
|
247
|
-
var os = require("os");
|
|
248
|
-
var execFileSync = require("child_process").execFileSync;
|
|
249
|
-
var baseDir;
|
|
250
|
-
if (config.osUsers) {
|
|
251
|
-
baseDir = "/var/clay/projects";
|
|
252
|
-
} else {
|
|
253
|
-
baseDir = config.projectsDir || path.join(REAL_HOME, "clay-projects");
|
|
254
|
-
}
|
|
255
|
-
try { fs.mkdirSync(baseDir, { recursive: true }); } catch (e) {}
|
|
256
|
-
// Generate slug and deduplicate
|
|
257
|
-
var slugs = config.projects.map(function (p) { return p.slug; });
|
|
258
|
-
var slug = generateSlug(path.join(baseDir, projectName), slugs);
|
|
259
|
-
var targetDir = path.join(baseDir, slug);
|
|
260
|
-
try {
|
|
261
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
262
|
-
// Run git init
|
|
263
|
-
if (config.osUsers && wsUser) {
|
|
264
|
-
var linuxUser = wsUser.linuxUser;
|
|
265
|
-
if (linuxUser) {
|
|
266
|
-
var uidGid = null;
|
|
267
|
-
try {
|
|
268
|
-
uidGid = {
|
|
269
|
-
uid: parseInt(execFileSync("id", ["-u", linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10),
|
|
270
|
-
gid: parseInt(execFileSync("id", ["-g", linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10),
|
|
271
|
-
};
|
|
272
|
-
} catch (e) {}
|
|
273
|
-
if (uidGid) {
|
|
274
|
-
fs.chmodSync(targetDir, 0o700);
|
|
275
|
-
execFileSync("chown", ["-R", linuxUser + ":" + linuxUser, targetDir]);
|
|
276
|
-
var gitInit = osUsersMod.wrapSpawnAsUser("git", ["init"], { cwd: targetDir, uid: uidGid.uid, gid: uidGid.gid, env: { PATH: "/usr/local/bin:/usr/bin:/bin" } });
|
|
277
|
-
execFileSync(gitInit.command, gitInit.args, gitInit.options);
|
|
278
|
-
} else {
|
|
279
|
-
execFileSync("git", ["init"], { cwd: targetDir });
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
execFileSync("git", ["init"], { cwd: targetDir });
|
|
283
|
-
}
|
|
284
|
-
} else {
|
|
285
|
-
execFileSync("git", ["init"], { cwd: targetDir });
|
|
286
|
-
}
|
|
287
|
-
} catch (e) {
|
|
288
|
-
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (ce) {}
|
|
289
|
-
return { ok: false, error: "Failed to create project: " + e.message };
|
|
290
|
-
}
|
|
291
|
-
// Register project - creator always becomes owner, default private
|
|
292
|
-
var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
293
|
-
projectEntry.projectKnowledgeId = projectLogContext.createProjectKnowledgeId();
|
|
294
|
-
if (wsUser && wsUser.id) {
|
|
295
|
-
projectEntry.ownerId = wsUser.id;
|
|
296
|
-
}
|
|
297
|
-
relay.addProject(targetDir, slug, null, null, wsUser ? wsUser.id : null, null, projectRuntimeOptions(projectEntry));
|
|
298
|
-
config.projects.push(projectEntry);
|
|
299
|
-
saveConfig(config);
|
|
300
|
-
try { syncClayrc(config.projects); } catch (e) {}
|
|
301
|
-
console.log("[daemon] Created project:", slug, "→", targetDir, "entry:", JSON.stringify({ ownerId: projectEntry.ownerId, visibility: projectEntry.visibility }));
|
|
302
|
-
// OS users mode: grant ACL
|
|
303
|
-
if (config.osUsers && wsUser && wsUser.linuxUser) {
|
|
304
|
-
console.log("[daemon] Granting ACL:", targetDir, "→", wsUser.linuxUser);
|
|
305
|
-
grantProjectAccess(targetDir, wsUser.linuxUser);
|
|
306
|
-
} else if (config.osUsers) {
|
|
307
|
-
console.log("[daemon] Skipping ACL grant: osUsers=true but linuxUser=", wsUser && wsUser.linuxUser);
|
|
308
|
-
}
|
|
309
|
-
relay.broadcastAll({
|
|
310
|
-
type: "projects_updated",
|
|
311
|
-
projects: relay.getProjects(),
|
|
312
|
-
projectCount: config.projects.length,
|
|
313
|
-
});
|
|
314
|
-
return { ok: true, slug: slug };
|
|
315
|
-
},
|
|
210
|
+
onAddProject: lifecycleCallbacks.onAddProject,
|
|
211
|
+
onCreateProject: lifecycleCallbacks.onCreateProject,
|
|
316
212
|
onCloneProject: function (cloneUrl, wsUser, callback) {
|
|
213
|
+
wsUser = wsUser && usersModule.findUserById(wsUser.id);
|
|
214
|
+
if (usersModule.isMultiUser() && !wsUser) { callback({ ok: false, error: "A valid account is required." }); return; }
|
|
317
215
|
var os = require("os");
|
|
318
216
|
var spawn = require("child_process").spawn;
|
|
319
217
|
var execFileSync = require("child_process").execFileSync;
|
|
@@ -323,12 +221,15 @@ var relay = createServer({
|
|
|
323
221
|
} else {
|
|
324
222
|
baseDir = config.projectsDir || path.join(REAL_HOME, "clay-projects");
|
|
325
223
|
}
|
|
326
|
-
try { fs.mkdirSync(baseDir, { recursive: true }); } catch (e) {}
|
|
327
224
|
// Derive slug from repo URL
|
|
328
225
|
var repoName = cloneUrl.replace(/\.git$/, "").split("/").pop() || "project";
|
|
329
226
|
var slugs = config.projects.map(function (p) { return p.slug; });
|
|
330
227
|
var slug = generateSlug(path.join(baseDir, repoName), slugs);
|
|
331
|
-
var targetDir
|
|
228
|
+
var targetDir;
|
|
229
|
+
var directoryClaim;
|
|
230
|
+
var settled = false;
|
|
231
|
+
function finish(result) { if (settled) return; settled = true; callback(result); }
|
|
232
|
+
function cleanup() { if (directoryClaim) { try { directoryClaim.cleanup(); } catch (error) {} } }
|
|
332
233
|
// Clone as user to use their git credentials
|
|
333
234
|
var spawnOpts = { cwd: baseDir };
|
|
334
235
|
if (config.osUsers && wsUser && wsUser.linuxUser) {
|
|
@@ -341,22 +242,23 @@ var relay = createServer({
|
|
|
341
242
|
});
|
|
342
243
|
} catch (e) {}
|
|
343
244
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
245
|
+
try {
|
|
246
|
+
directoryClaim = require("./project-directory-claim").claim({ baseDir: baseDir, slug: slug, projects: config.projects, userId: wsUser && wsUser.id });
|
|
247
|
+
targetDir = directoryClaim.path;
|
|
248
|
+
directoryClaim.assertOwned();
|
|
249
|
+
if (config.osUsers && wsUser && wsUser.linuxUser && spawnOpts.uid != null) fs.chownSync(targetDir, spawnOpts.uid, spawnOpts.gid);
|
|
250
|
+
directoryClaim.assertOwned();
|
|
251
|
+
} catch (error) {
|
|
252
|
+
cleanup();
|
|
253
|
+
finish({ ok: false, error: "Failed to prepare project directory: " + error.message });
|
|
347
254
|
return;
|
|
348
255
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
var cloneSpawn = osUsersMod.wrapSpawnAsUser("git", ["clone", cloneUrl, targetDir], spawnOpts);
|
|
359
|
-
var proc = spawn(cloneSpawn.command, cloneSpawn.args, cloneSpawn.options);
|
|
256
|
+
var proc;
|
|
257
|
+
try {
|
|
258
|
+
directoryClaim.assertOwned();
|
|
259
|
+
var cloneSpawn = osUsersMod.wrapSpawnAsUser("git", ["clone", cloneUrl, targetDir], spawnOpts);
|
|
260
|
+
proc = spawn(cloneSpawn.command, cloneSpawn.args, cloneSpawn.options);
|
|
261
|
+
} catch (error) { cleanup(); finish({ ok: false, error: "Failed to start git clone: " + error.message }); return; }
|
|
360
262
|
var stderrBuf = "";
|
|
361
263
|
proc.stderr.on("data", function (chunk) { stderrBuf += chunk.toString(); });
|
|
362
264
|
// 5 minute timeout
|
|
@@ -365,19 +267,23 @@ var relay = createServer({
|
|
|
365
267
|
}, 5 * 60 * 1000);
|
|
366
268
|
proc.on("close", function (code) {
|
|
367
269
|
clearTimeout(cloneTimeout);
|
|
270
|
+
if (settled) return;
|
|
271
|
+
try { directoryClaim.assertOwned(); } catch (error) { finish({ ok: false, error: error.message }); return; }
|
|
368
272
|
if (code !== 0) {
|
|
369
|
-
|
|
273
|
+
cleanup();
|
|
370
274
|
var errMsg = stderrBuf.trim().split("\n").pop() || "Clone failed (exit code " + code + ")";
|
|
371
|
-
|
|
275
|
+
finish({ ok: false, error: errMsg });
|
|
372
276
|
return;
|
|
373
277
|
}
|
|
374
278
|
// chown and restrict permissions if osUsers
|
|
375
279
|
if (config.osUsers && wsUser && wsUser.linuxUser) {
|
|
376
280
|
try {
|
|
281
|
+
directoryClaim.assertOwned();
|
|
377
282
|
fs.chmodSync(targetDir, 0o700);
|
|
378
283
|
execFileSync("chown", ["-R", wsUser.linuxUser + ":" + wsUser.linuxUser, targetDir]);
|
|
379
284
|
} catch (e) {}
|
|
380
285
|
}
|
|
286
|
+
if (usersModule.isMultiUser() && (!wsUser || !usersModule.findUserById(wsUser.id))) { finish({ ok: false, error: "The creating account is no longer available." }); return; }
|
|
381
287
|
// Register project - creator always becomes owner
|
|
382
288
|
// Creator always becomes owner, default private
|
|
383
289
|
var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
@@ -385,9 +291,11 @@ var relay = createServer({
|
|
|
385
291
|
if (wsUser && wsUser.id) {
|
|
386
292
|
projectEntry.ownerId = wsUser.id;
|
|
387
293
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
294
|
+
try {
|
|
295
|
+
directoryClaim.assertOwned();
|
|
296
|
+
config = require("./project-ownership").register(config, projectEntry, usersModule.getAllUsers(), saveConfig, osUsersMod.resolveOsUserInfo);
|
|
297
|
+
} catch (error) { finish({ ok: false, error: "Failed to register project: " + error.message }); return; }
|
|
298
|
+
relay.addProject(targetDir, slug, null, null, projectEntry.ownerId || null, null, projectRuntimeOptions(projectEntry));
|
|
391
299
|
try { syncClayrc(config.projects); } catch (e) {}
|
|
392
300
|
console.log("[daemon] Cloned project:", slug, "→", targetDir);
|
|
393
301
|
if (config.osUsers && wsUser && wsUser.linuxUser) {
|
|
@@ -398,12 +306,13 @@ var relay = createServer({
|
|
|
398
306
|
projects: relay.getProjects(),
|
|
399
307
|
projectCount: config.projects.length,
|
|
400
308
|
});
|
|
401
|
-
|
|
309
|
+
finish({ ok: true, slug: slug });
|
|
402
310
|
});
|
|
403
311
|
proc.on("error", function (err) {
|
|
404
312
|
clearTimeout(cloneTimeout);
|
|
405
|
-
|
|
406
|
-
|
|
313
|
+
if (settled) return;
|
|
314
|
+
cleanup();
|
|
315
|
+
finish({ ok: false, error: "Failed to start git clone: " + err.message });
|
|
407
316
|
});
|
|
408
317
|
},
|
|
409
318
|
onRemoveProject: function (slug, userId) {
|
|
@@ -444,40 +353,7 @@ var relay = createServer({
|
|
|
444
353
|
});
|
|
445
354
|
return { ok: true };
|
|
446
355
|
}
|
|
447
|
-
|
|
448
|
-
for (var j = 0; j < config.projects.length; j++) {
|
|
449
|
-
if (config.projects[j].slug === slug) { found = config.projects[j]; break; }
|
|
450
|
-
}
|
|
451
|
-
if (!found) return { ok: false, error: "Project not found" };
|
|
452
|
-
// Cascade remove worktrees belonging to this parent
|
|
453
|
-
cleanupWorktreesForParent(relay, slug);
|
|
454
|
-
// Save to removedProjects for re-add functionality
|
|
455
|
-
if (!config.removedProjects) config.removedProjects = [];
|
|
456
|
-
config.removedProjects.push({
|
|
457
|
-
path: found.path,
|
|
458
|
-
title: found.title || null,
|
|
459
|
-
icon: found.icon || null,
|
|
460
|
-
userId: userId || null,
|
|
461
|
-
projectKnowledgeId: found.projectKnowledgeId || null,
|
|
462
|
-
removedAt: Date.now(),
|
|
463
|
-
});
|
|
464
|
-
// Cap at 20 entries (oldest first)
|
|
465
|
-
if (config.removedProjects.length > 20) {
|
|
466
|
-
config.removedProjects = config.removedProjects.slice(config.removedProjects.length - 20);
|
|
467
|
-
}
|
|
468
|
-
relay.removeProject(slug);
|
|
469
|
-
config.projects = config.projects.filter(function (p) { return p.slug !== slug; });
|
|
470
|
-
saveConfig(config);
|
|
471
|
-
// Remove from .clayrc so it doesn't appear in restore prompt
|
|
472
|
-
if (found.path) { try { removeFromClayrc(found.path); } catch (e) {} }
|
|
473
|
-
try { syncClayrc(config.projects); } catch (e) {}
|
|
474
|
-
console.log("[daemon] Removed project (web):", slug);
|
|
475
|
-
relay.broadcastAll({
|
|
476
|
-
type: "projects_updated",
|
|
477
|
-
projects: relay.getProjects(),
|
|
478
|
-
projectCount: config.projects.length,
|
|
479
|
-
});
|
|
480
|
-
return { ok: true };
|
|
356
|
+
return lifecycleCallbacks.onRemoveProject(slug, userId);
|
|
481
357
|
},
|
|
482
358
|
onReorderProjects: function (slugs) {
|
|
483
359
|
// Build a slug->project map from current projects
|
|
@@ -511,25 +387,8 @@ var relay = createServer({
|
|
|
511
387
|
return { ok: true };
|
|
512
388
|
},
|
|
513
389
|
onSetProjectTitle: function (slug, newTitle) {
|
|
514
|
-
relay.setProjectTitle(slug, newTitle);
|
|
515
|
-
|
|
516
|
-
if (config.projects[ti].slug === slug) {
|
|
517
|
-
if (newTitle) {
|
|
518
|
-
config.projects[ti].title = newTitle;
|
|
519
|
-
} else {
|
|
520
|
-
delete config.projects[ti].title;
|
|
521
|
-
}
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
saveConfig(config);
|
|
526
|
-
try { syncClayrc(config.projects); } catch (e) {}
|
|
527
|
-
relay.broadcastAll({
|
|
528
|
-
type: "projects_updated",
|
|
529
|
-
projects: relay.getProjects(),
|
|
530
|
-
projectCount: config.projects.length,
|
|
531
|
-
});
|
|
532
|
-
return { ok: true };
|
|
390
|
+
if (isWorktreeSlug(slug)) { relay.setProjectTitle(slug, newTitle); return { ok: true }; }
|
|
391
|
+
return lifecycleCallbacks.onSetProjectTitle(slug, newTitle);
|
|
533
392
|
},
|
|
534
393
|
onSetProjectIcon: function (slug, newIcon) {
|
|
535
394
|
relay.setProjectIcon(slug, newIcon);
|
|
@@ -553,30 +412,28 @@ var relay = createServer({
|
|
|
553
412
|
return { ok: true };
|
|
554
413
|
},
|
|
555
414
|
onSetProjectSessionVisibilityDefault: function (slug, visibility) {
|
|
556
|
-
|
|
557
|
-
if (result.error) return { ok: false, error: result.error };
|
|
558
|
-
saveConfig(config);
|
|
559
|
-
relay.broadcastAll({ type: "projects_updated", projects: relay.getProjects(), projectCount: config.projects.length });
|
|
560
|
-
return result;
|
|
415
|
+
return projectLifecycle.update(slug, "session_visibility", visibility);
|
|
561
416
|
},
|
|
562
417
|
onProjectOwnerChanged: function (slug, ownerId) {
|
|
418
|
+
if (!config.osUsers) return projectLifecycle.update(slug, "owner", ownerId);
|
|
563
419
|
if (slug.indexOf("mate-") === 0) return {error:"Mate ownership cannot be transferred."};
|
|
420
|
+
if (!ownerId || !usersModule.findUserById(ownerId)) return { error: "A valid owner is required." };
|
|
564
421
|
console.log("[daemon] onProjectOwnerChanged:", slug, "→", ownerId);
|
|
422
|
+
var previousConfig = JSON.parse(JSON.stringify(config));
|
|
565
423
|
var oldOwnerId = null;
|
|
566
424
|
var projectIdx = -1;
|
|
567
425
|
for (var oi = 0; oi < config.projects.length; oi++) {
|
|
568
426
|
if (config.projects[oi].slug === slug) {
|
|
569
427
|
oldOwnerId = config.projects[oi].ownerId || null;
|
|
570
428
|
projectIdx = oi;
|
|
571
|
-
|
|
572
|
-
config.projects[oi].ownerId = ownerId;
|
|
573
|
-
} else {
|
|
574
|
-
delete config.projects[oi].ownerId;
|
|
575
|
-
}
|
|
429
|
+
config.projects[oi].ownerId = ownerId;
|
|
576
430
|
break;
|
|
577
431
|
}
|
|
578
432
|
}
|
|
579
|
-
saveConfig(config);
|
|
433
|
+
try { saveConfig(config); } catch (error) { config = previousConfig; throw error; }
|
|
434
|
+
relay.forEachProject(function (project, projectSlug) {
|
|
435
|
+
if (projectSlug === slug || daemonProjectAccess.parentSlugFor(projectSlug) === slug) project.setProjectOwner(ownerId);
|
|
436
|
+
});
|
|
580
437
|
// OS users mode: revoke old owner ACL, grant new owner ACL
|
|
581
438
|
if (config.osUsers && projectIdx >= 0) {
|
|
582
439
|
var projectPath = config.projects[projectIdx].path;
|
|
@@ -615,18 +472,7 @@ var relay = createServer({
|
|
|
615
472
|
return { envrc: "" };
|
|
616
473
|
},
|
|
617
474
|
onSetProjectEnv: function (slug, envrc) {
|
|
618
|
-
|
|
619
|
-
if (config.projects[ei].slug === slug) {
|
|
620
|
-
if (envrc) {
|
|
621
|
-
config.projects[ei].envrc = envrc;
|
|
622
|
-
} else {
|
|
623
|
-
delete config.projects[ei].envrc;
|
|
624
|
-
}
|
|
625
|
-
saveConfig(config);
|
|
626
|
-
return { ok: true };
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
return { ok: false, error: "Project not found" };
|
|
475
|
+
return lifecycleCallbacks.onSetProjectEnv(slug, envrc);
|
|
630
476
|
},
|
|
631
477
|
onGetSharedEnv: function () {
|
|
632
478
|
return { envrc: config.sharedEnv || "" };
|
|
@@ -927,6 +773,7 @@ var relay = createServer({
|
|
|
927
773
|
spawnAndRestart();
|
|
928
774
|
},
|
|
929
775
|
onSetProjectVisibility: function (slug, visibility) {
|
|
776
|
+
if (!config.osUsers) return projectLifecycle.update(slug, "visibility", visibility);
|
|
930
777
|
if (slug.indexOf("mate-") === 0) return {error:"Mates cannot be shared."};
|
|
931
778
|
for (var i = 0; i < config.projects.length; i++) {
|
|
932
779
|
if (config.projects[i].slug === slug) {
|
|
@@ -962,6 +809,7 @@ var relay = createServer({
|
|
|
962
809
|
return { error: "Project not found" };
|
|
963
810
|
},
|
|
964
811
|
onSetProjectAllowedUsers: function (slug, allowedUsers) {
|
|
812
|
+
if (!config.osUsers && !daemonProjectAccess.parentSlugFor(slug)) return projectLifecycle.update(slug, "allowed_users", allowedUsers);
|
|
965
813
|
var accessBefore = daemonProjectAccess.getProjectAccess(config, slug, config.osUsers);
|
|
966
814
|
var result = daemonProjectAccess.setAllowedUsers(config, slug, allowedUsers);
|
|
967
815
|
if (result.error) return result;
|
|
@@ -1131,11 +979,11 @@ var ipc = createIPCServer(socketPath(), function (msg) {
|
|
|
1131
979
|
}
|
|
1132
980
|
var slugs = config.projects.map(function (p) { return p.slug; });
|
|
1133
981
|
var slug = generateSlug(absPath, slugs);
|
|
1134
|
-
var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
982
|
+
var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private", ownershipRecovery: "required" };
|
|
1135
983
|
projectEntry.projectKnowledgeId = rememberedProjectKnowledgeId(absPath);
|
|
1136
|
-
|
|
1137
|
-
config.projects.
|
|
1138
|
-
|
|
984
|
+
config = require("./project-ownership").register(config, projectEntry, usersModule.getAllUsers(), saveConfig, osUsersMod.resolveOsUserInfo);
|
|
985
|
+
var registeredProject = config.projects[config.projects.length - 1];
|
|
986
|
+
relay.addProject(absPath, slug, null, null, registeredProject.ownerId || null, null, projectRuntimeOptions(registeredProject));
|
|
1139
987
|
try { syncClayrc(config.projects); } catch (e) {}
|
|
1140
988
|
console.log("[daemon] Added project:", slug, "→", absPath);
|
|
1141
989
|
// Discover and register worktrees for the new project
|
|
@@ -1367,6 +1215,10 @@ var ipc = createIPCServer(socketPath(), function (msg) {
|
|
|
1367
1215
|
var projects = config.projects || [];
|
|
1368
1216
|
for (var i = 0; i < projects.length; i++) {
|
|
1369
1217
|
var p = projects[i];
|
|
1218
|
+
if (!projectLifecycle.canRestore(p)) {
|
|
1219
|
+
console.log("[daemon] Project removal needs recovery; runtime remains stopped:", p.slug);
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1370
1222
|
if (fs.existsSync(p.path)) {
|
|
1371
1223
|
console.log("[daemon] Adding project:", p.slug, "→", p.path);
|
|
1372
1224
|
relay.addProject(p.path, p.slug, p.title, p.icon, p.ownerId, null, projectRuntimeOptions(p));
|
|
@@ -1413,7 +1265,7 @@ if (usersModule.isMultiUser()) {
|
|
|
1413
1265
|
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
1414
1266
|
var mateSlug = "mate-" + m.id;
|
|
1415
1267
|
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
1416
|
-
if (fs.existsSync(mateDir)) {
|
|
1268
|
+
if (!m.lifecycleDeletion && fs.existsSync(mateDir)) {
|
|
1417
1269
|
var mateDef = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
|
|
1418
1270
|
var isHost = !!(mateDef && mateDef.hostAgent);
|
|
1419
1271
|
console.log("[daemon] Adding mate project:", mateSlug + (isHost ? " (host agent)" : ""));
|
|
@@ -1430,7 +1282,7 @@ if (usersModule.isMultiUser()) {
|
|
|
1430
1282
|
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
1431
1283
|
var mateSlug = "mate-" + m.id;
|
|
1432
1284
|
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
1433
|
-
if (fs.existsSync(mateDir)) {
|
|
1285
|
+
if (!m.lifecycleDeletion && fs.existsSync(mateDir)) {
|
|
1434
1286
|
var mateDef2 = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
|
|
1435
1287
|
var isHost2 = !!(mateDef2 && mateDef2.hostAgent);
|
|
1436
1288
|
console.log("[daemon] Adding mate project:", mateSlug + (isHost2 ? " (host agent)" : ""));
|
package/lib/issues-service.js
CHANGED
|
@@ -129,25 +129,35 @@ function attachIssuesService(ctx) {
|
|
|
129
129
|
principal: principal, worktreeOnly: authorization.worktreeOnly };
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function bind(source, isSession) {
|
|
132
|
+
function bind(source, isSession, canonicalClay) {
|
|
133
133
|
if (!source || typeof source.projectSlug !== "string") return null;
|
|
134
134
|
var initialSlug = source.projectSlug;
|
|
135
135
|
var initialProject = projectFor(initialSlug);
|
|
136
136
|
var initialSession = isSession ? source.session : null;
|
|
137
|
+
var clayInitial;
|
|
138
|
+
if (canonicalClay) { try { clayInitial = require("./canonical-clay-record-authority").resolve(source.authority, initialSlug); } catch (error) { return null; } }
|
|
137
139
|
if (!initialProject || (isSession && !initialSession)) return null;
|
|
138
140
|
if (initialSession) provenance.ensureOrigin(initialSession);
|
|
139
|
-
var initialUserId = initialSession ? initialSession.ownerId || null : source.userId || null;
|
|
141
|
+
var initialUserId = initialSession ? initialSession.ownerId || null : canonicalClay ? clayInitial.principal.userId || clayInitial.principal.singleUserOwnerId : source.userId || null;
|
|
140
142
|
var initialOrigin = initialSession ? initialSession.sessionOriginId || null : null;
|
|
141
143
|
var initial = bindingState(initialProject, initialSession, initialUserId, initialOrigin);
|
|
142
144
|
if (!initial) return null;
|
|
143
145
|
|
|
144
146
|
function current() {
|
|
145
147
|
if (projectFor(initialSlug) !== initialProject) throw new Error("This Issues binding is no longer valid.");
|
|
148
|
+
if (canonicalClay) {
|
|
149
|
+
var clayLive = require("./canonical-clay-record-authority").resolve(source.authority, initialSlug);
|
|
150
|
+
if (clayLive.project !== initialProject || clayLive.status.path !== clayInitial.status.path || clayLive.status.projectKnowledgeId !== clayInitial.status.projectKnowledgeId || clayLive.status.changeSetId !== clayInitial.status.changeSetId) throw new Error("Selected Issues context changed.");
|
|
151
|
+
}
|
|
146
152
|
var live = bindingState(initialProject, initialSession, initialUserId, initialOrigin);
|
|
147
153
|
if (!live) throw new Error("This Issues binding is no longer valid.");
|
|
148
154
|
return live;
|
|
149
155
|
}
|
|
150
156
|
|
|
157
|
+
function expected(entry, args) { if (canonicalClay && (!Number.isInteger(args.expectedRevision) || entry.revision !== args.expectedRevision)) throw new Error("Issue revision conflict."); }
|
|
158
|
+
|
|
159
|
+
function actor(state) { return canonicalClay ? require("./canonical-clay-record-authority").resolve(source.authority, initialSlug).actor : author(state.user, state.session ? "session" : "user", state.session); }
|
|
160
|
+
|
|
151
161
|
function visibleIssue(store, ref, state) {
|
|
152
162
|
var result = store.read(ref);
|
|
153
163
|
if (state.worktreeOnly && (!result.context || result.context.changeSetId !== state.status.changeSetId)) throw new Error("Issue not found.");
|
|
@@ -174,7 +184,7 @@ function attachIssuesService(ctx) {
|
|
|
174
184
|
path: state.status.path, knowledgeId: governing.projectKnowledgeId,
|
|
175
185
|
changeSetId: state.status.changeSetId || null, worktreeOnly: state.worktreeOnly,
|
|
176
186
|
recordContext: clone(issueContext(state.status)),
|
|
177
|
-
actor: clone(
|
|
187
|
+
actor: clone(actor(state)),
|
|
178
188
|
osIdentity: needsIdentity ? osIdentity(state) : null };
|
|
179
189
|
}
|
|
180
190
|
|
|
@@ -184,7 +194,7 @@ function attachIssuesService(ctx) {
|
|
|
184
194
|
var liveIdentity = needsIdentity ? osIdentity(fresh) : null;
|
|
185
195
|
if (fresh.project !== before.project || fresh.session !== before.session || (fresh.user.id || null) !== before.userId ||
|
|
186
196
|
fresh.status.path !== before.path || !governing || governing.projectKnowledgeId !== before.knowledgeId ||
|
|
187
|
-
(fresh.status.changeSetId || null) !== before.changeSetId || fresh.worktreeOnly !== before.worktreeOnly ||
|
|
197
|
+
(fresh.status.changeSetId || null) !== before.changeSetId || fresh.worktreeOnly !== before.worktreeOnly || JSON.stringify(issueContext(fresh.status)) !== JSON.stringify(before.recordContext) ||
|
|
188
198
|
JSON.stringify(liveIdentity) !== JSON.stringify(before.osIdentity)) throw new Error("Issue authorization changed during the operation.");
|
|
189
199
|
return fresh;
|
|
190
200
|
}
|
|
@@ -200,7 +210,7 @@ function attachIssuesService(ctx) {
|
|
|
200
210
|
var github = require('./issues-github').attachIssueGithub({
|
|
201
211
|
locks: githubLocks, assessments: githubAssessments, run: context.runGithub, analyze: source.analyzeGithub || context.analyzeGithub,
|
|
202
212
|
begin: function () {
|
|
203
|
-
if (isSession) throw new Error('GitHub filing requires an explicit action in the Issues panel.');
|
|
213
|
+
if (isSession || canonicalClay) throw new Error('GitHub filing requires an explicit action in the Issues panel.');
|
|
204
214
|
return operation(current(), true);
|
|
205
215
|
},
|
|
206
216
|
read: function (before, ref) {
|
|
@@ -217,6 +227,7 @@ function attachIssuesService(ctx) {
|
|
|
217
227
|
return {
|
|
218
228
|
searchGithubIssue: github.search, linkGithubIssue: github.link, createGithubIssue: github.create,
|
|
219
229
|
linkWorkSession: function (args, session) {
|
|
230
|
+
if (canonicalClay) throw new Error("Use reviewed project assignment or follow-up for Driver execution.");
|
|
220
231
|
var state = current();
|
|
221
232
|
var manager = state.project.getSessionManager();
|
|
222
233
|
if (!args || !Number.isInteger(args.expectedRevision)) throw new Error("expectedRevision is required.");
|
|
@@ -232,9 +243,10 @@ function attachIssuesService(ctx) {
|
|
|
232
243
|
if (links.some(function (link) { return link.sessionId === session.sessionOriginId; })) return entry;
|
|
233
244
|
var link = { sessionId: session.sessionOriginId, role: "driver", issueRevision: args.expectedRevision };
|
|
234
245
|
if (args.requestId !== undefined) link.requestId = schema.text(args.requestId, "work request id", 200, true);
|
|
235
|
-
return target.update(args.ref, { linkedWorkSessions: links.concat([link]), status: "in_progress" }, args.expectedRevision, author(state.user, "user", null));
|
|
246
|
+
return target.update(args.ref, { linkedWorkSessions: links.concat([link]), status: "in_progress" }, args.expectedRevision, canonicalClay ? actor(state) : author(state.user, "user", null));
|
|
236
247
|
},
|
|
237
248
|
resolveWorkSession: function (args) {
|
|
249
|
+
if (canonicalClay) throw new Error("Work sessions require the existing project Driver workflow.");
|
|
238
250
|
var state = current();
|
|
239
251
|
var entry = visibleIssue(openStore(state.status), args.ref, state);
|
|
240
252
|
if (!(entry.linkedWorkSessions || []).some(function (link) { return link.sessionId === args.sessionOriginId; })) throw new Error("Work session unavailable.");
|
|
@@ -255,9 +267,9 @@ function attachIssuesService(ctx) {
|
|
|
255
267
|
readIssueTombstone: function (args) { var state = current(); return scopedIssue(openStore(state.status), args && args.ref, state); },
|
|
256
268
|
issueHistory: function (args) { var state = current(); var store = openStore(state.status); var history = store.history(args && args.ref); if (state.worktreeOnly) { var historical = scopedIssue(store, args && args.ref, state); if (!historical.context || historical.context.changeSetId !== state.status.changeSetId) throw new Error("Issue not found."); } return history; },
|
|
257
269
|
readIssueRevision: function (args) { var state = current(); var store = openStore(state.status); visibleIssue(store, args && args.ref, state); return store.readRevision(args.ref, args.revision); },
|
|
258
|
-
commentIssue: function (args) { var state = current(); var target = openStore(state.status); visibleIssue(target, args && args.ref, state); return target.comment(args.ref, args.body, author(state.user, "user", null)); },
|
|
270
|
+
commentIssue: function (args) { var state = current(); var target = openStore(state.status); expected(visibleIssue(target, args && args.ref, state), args); return target.comment(args.ref, args.body, canonicalClay ? actor(state) : author(state.user, "user", null)); },
|
|
259
271
|
listIssueFeedback: function (args) {
|
|
260
|
-
if (!isSession) throw new Error("Only the Project Driver can review issue comments.");
|
|
272
|
+
if (!isSession && !canonicalClay) throw new Error("Only the Project Driver can review issue comments.");
|
|
261
273
|
var state = current(); var target = openStore(state.status); var out = []; var feedbackLimit = args && args.limit === undefined ? 200 : Number(args.limit);
|
|
262
274
|
if (!Number.isInteger(feedbackLimit) || feedbackLimit < 1 || feedbackLimit > 200) throw new Error("Feedback limit must be between 1 and 200.");
|
|
263
275
|
var position = { issueCursor: null, issueIndex: 0, commentOffset: 0 };
|
|
@@ -297,31 +309,32 @@ function attachIssuesService(ctx) {
|
|
|
297
309
|
return { feedback: out, total: out.length, nextCursor: nextCursor, truncated: !!nextCursor };
|
|
298
310
|
},
|
|
299
311
|
reviewIssueComment: function (args) {
|
|
300
|
-
if (!isSession) throw new Error("Only the Project Driver can review issue comments.");
|
|
312
|
+
if (!isSession && !canonicalClay) throw new Error("Only the Project Driver can review issue comments.");
|
|
301
313
|
var state = current(); var target = openStore(state.status); var entry = visibleIssue(target, args && args.ref, state);
|
|
314
|
+
expected(entry, args);
|
|
302
315
|
if (args.action === "incorporate") {
|
|
303
316
|
if (!Number.isInteger(args.expectedRevision)) throw new Error("expectedRevision is required to incorporate a comment.");
|
|
304
317
|
var data = cleanPublic(args, { ref: true, expectedRevision: true, commentId: true, action: true, response: true, commitSha: true }, true);
|
|
305
318
|
if (data.status === "resolved") throw new Error("Use update_issue with verified commit evidence before resolving this Issue.");
|
|
306
|
-
return target.incorporate(entry.ref, args.commentId, data, args.expectedRevision, args.response,
|
|
319
|
+
return target.incorporate(entry.ref, args.commentId, data, args.expectedRevision, args.response, actor(state));
|
|
307
320
|
}
|
|
308
|
-
return target.review(entry.ref, args.commentId, args.action, args.response,
|
|
321
|
+
return target.review(entry.ref, args.commentId, args.action, args.response, actor(state));
|
|
309
322
|
},
|
|
310
323
|
removeIssue: function (args) {
|
|
311
324
|
var state = current();
|
|
312
325
|
if (!state.principal.isProjectOwner) throw new Error("Only the project owner can delete Issues.");
|
|
313
326
|
var target = openStore(state.status); var entry = visibleIssue(target, args && args.ref, state);
|
|
314
327
|
if (!Number.isInteger(args && args.expectedRevision) || entry.revision !== args.expectedRevision) throw new Error("Issue revision conflict.");
|
|
315
|
-
return target.remove(entry.ref, author(state.user, "user", null), args.expectedRevision);
|
|
328
|
+
return target.remove(entry.ref, canonicalClay ? actor(state) : author(state.user, "user", null), args.expectedRevision);
|
|
316
329
|
},
|
|
317
330
|
createIssue: function (args) {
|
|
318
|
-
if (!isSession) throw new Error("People cannot create issues directly. Ask the Project Driver to create one.");
|
|
331
|
+
if (!isSession && !canonicalClay) throw new Error("People cannot create issues directly. Ask the Project Driver to create one.");
|
|
319
332
|
var data = cleanPublic(args, { commitSha: true }, true);
|
|
320
333
|
var status = data.status === undefined ? "open" : data.status;
|
|
321
334
|
if (schema.STATUSES.indexOf(status) === -1) throw new Error("Invalid status.");
|
|
322
335
|
var state = current();
|
|
323
336
|
if (status !== "resolved") return Promise.resolve(openStore(state.status).create(data,
|
|
324
|
-
|
|
337
|
+
actor(state), issueContext(state.status)));
|
|
325
338
|
if (typeof data.resolutionSummary !== "string" || !data.resolutionSummary.trim()) throw new Error("resolutionSummary is required when resolving an issue.");
|
|
326
339
|
var before = operation(state, true);
|
|
327
340
|
return evidence(before, args.commitSha).then(function (proof) {
|
|
@@ -340,7 +353,7 @@ function attachIssuesService(ctx) {
|
|
|
340
353
|
var existing = visibleIssue(target, ref, state);
|
|
341
354
|
if (existing.revision !== expectedRevision) throw new Error("Issue revision conflict.");
|
|
342
355
|
if (data.status !== "resolved") return Promise.resolve(target.update(ref, data, expectedRevision,
|
|
343
|
-
|
|
356
|
+
actor(state)));
|
|
344
357
|
if (typeof data.resolutionSummary !== "string" || !data.resolutionSummary.trim()) throw new Error("resolutionSummary is required when resolving an issue.");
|
|
345
358
|
var before = operation(state, true);
|
|
346
359
|
return evidence(before, args.commitSha).then(function (proof) {
|
|
@@ -354,6 +367,7 @@ function attachIssuesService(ctx) {
|
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
return { bindUser: function (source) { return bind(source, false); },
|
|
370
|
+
bindCanonicalClay: function (source) { return bind(source, false, true); },
|
|
357
371
|
bindProjectSession: function (source) { return bind(source, true); },
|
|
358
372
|
issueScopeIdForKnowledgeId: issuesStore.issueScopeIdForKnowledgeId };
|
|
359
373
|
}
|
|
@@ -38,9 +38,10 @@ function attachKnowledgeDbImport(ctx) {
|
|
|
38
38
|
}
|
|
39
39
|
function importRecords(args) {
|
|
40
40
|
// Snapshot caller-owned JSON before any asynchronous operation.
|
|
41
|
-
var batch = ctx.clone(input.batch(args)); args = ctx.clone(args);
|
|
41
|
+
var revalidate = args.revalidate; var batch = ctx.clone(input.batch(args)); args = ctx.clone(args); args.revalidate = revalidate;
|
|
42
42
|
var operationId = ctx.mutationId(args.operationId); var fingerprint = ctx.fingerprint("import", args);
|
|
43
43
|
return ctx.serialize(args.databaseId, function () { return ctx.load(args.databaseId).then(async function (current) {
|
|
44
|
+
ctx.guard(args, current);
|
|
44
45
|
var duplicate = ctx.receipt(current, operationId, fingerprint);
|
|
45
46
|
if (duplicate) return Object.assign({}, duplicate, { projectionPending: current.projectionPending === true, projectionError: current.projectionError || null });
|
|
46
47
|
if (current.archivedAt) throw ctx.error("Knowledge database is archived.", "ARCHIVED", current.revision);
|
|
@@ -60,10 +61,10 @@ function attachKnowledgeDbImport(ctx) {
|
|
|
60
61
|
// payload still conflicts. Preserve record values, archives and database revision.
|
|
61
62
|
if (!added.length) {
|
|
62
63
|
ctx.remember(next, operationId, fingerprint, result);
|
|
63
|
-
await ctx.replace(current, next);
|
|
64
|
+
await ctx.replace(current, next, args);
|
|
64
65
|
return Object.assign(result, { projectionPending: current.projectionPending === true, projectionError: current.projectionError || null });
|
|
65
66
|
}
|
|
66
|
-
var databases = await ctx.all(); var at = Date.now(); var who = ctx.actor(args.actor);
|
|
67
|
+
var databases = await ctx.all(); ctx.guard(args, current); var at = Date.now(); var who = ctx.actor(args.actor);
|
|
67
68
|
added.forEach(function (item) {
|
|
68
69
|
next.records.push({ id: item.mapping.recordId, revision: 1, values: {}, archivedAt: null, createdAt: at, updatedAt: at, createdBy: who, updatedBy: who });
|
|
69
70
|
next.recordHistory[item.mapping.recordId] = [];
|
|
@@ -78,7 +79,7 @@ function attachKnowledgeDbImport(ctx) {
|
|
|
78
79
|
next.revision++; next.updatedAt = at; next.updatedBy = who; next.projectionPending = true; next.projectionError = null;
|
|
79
80
|
result.databaseRevision = next.revision;
|
|
80
81
|
ctx.remember(next, operationId, fingerprint, result);
|
|
81
|
-
await ctx.replace(current, next);
|
|
82
|
+
await ctx.replace(current, next, args);
|
|
82
83
|
return ctx.finish(next, result, who);
|
|
83
84
|
}); });
|
|
84
85
|
}
|