@sapiom/harness 0.8.9 → 0.10.0
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/CHANGELOG.md +99 -0
- package/README.md +11 -1
- package/dist/core/agent-project-discovery.d.ts +235 -1
- package/dist/core/agent-project-discovery.d.ts.map +1 -1
- package/dist/core/agent-project-discovery.js +303 -1
- package/dist/core/agent-project-discovery.js.map +1 -1
- package/dist/core/canvas-interconnections.d.ts +31 -0
- package/dist/core/canvas-interconnections.d.ts.map +1 -1
- package/dist/core/canvas-interconnections.js +294 -21
- package/dist/core/canvas-interconnections.js.map +1 -1
- package/dist/core/canvas-render.d.ts +32 -0
- package/dist/core/canvas-render.d.ts.map +1 -1
- package/dist/core/canvas-render.js +50 -41
- package/dist/core/canvas-render.js.map +1 -1
- package/dist/core/definition-name.d.ts +12 -0
- package/dist/core/definition-name.d.ts.map +1 -1
- package/dist/core/definition-name.js +14 -5
- package/dist/core/definition-name.js.map +1 -1
- package/dist/core/system-graph-inventory.d.ts +68 -0
- package/dist/core/system-graph-inventory.d.ts.map +1 -0
- package/dist/core/system-graph-inventory.js +312 -0
- package/dist/core/system-graph-inventory.js.map +1 -0
- package/dist/core/system-graph-relationships.d.ts +41 -0
- package/dist/core/system-graph-relationships.d.ts.map +1 -0
- package/dist/core/system-graph-relationships.js +98 -0
- package/dist/core/system-graph-relationships.js.map +1 -0
- package/dist/core/system-graph-store.d.ts +45 -0
- package/dist/core/system-graph-store.d.ts.map +1 -0
- package/dist/core/system-graph-store.js +222 -0
- package/dist/core/system-graph-store.js.map +1 -0
- package/dist/core/system-graph-watcher.d.ts +51 -0
- package/dist/core/system-graph-watcher.d.ts.map +1 -0
- package/dist/core/system-graph-watcher.js +448 -0
- package/dist/core/system-graph-watcher.js.map +1 -0
- package/dist/core/system-graph.d.ts +43 -0
- package/dist/core/system-graph.d.ts.map +1 -0
- package/dist/core/system-graph.js +321 -0
- package/dist/core/system-graph.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +84 -6
- package/dist/core/workflow-registry.d.ts.map +1 -1
- package/dist/core/workflow-registry.js +191 -91
- package/dist/core/workflow-registry.js.map +1 -1
- package/dist/core/workspace-watcher.d.ts +21 -13
- package/dist/core/workspace-watcher.d.ts.map +1 -1
- package/dist/core/workspace-watcher.js +79 -81
- package/dist/core/workspace-watcher.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/agent-move.d.ts +134 -0
- package/dist/server/agent-move.d.ts.map +1 -0
- package/dist/server/agent-move.js +343 -0
- package/dist/server/agent-move.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +275 -22
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest.d.ts +3 -0
- package/dist/server/rest.d.ts.map +1 -1
- package/dist/server/rest.js +3 -0
- package/dist/server/rest.js.map +1 -1
- package/dist/server/studio-rail.d.ts +76 -0
- package/dist/server/studio-rail.d.ts.map +1 -0
- package/dist/server/studio-rail.js +234 -0
- package/dist/server/studio-rail.js.map +1 -0
- package/dist/server/system-graph.d.ts +14 -0
- package/dist/server/system-graph.d.ts.map +1 -0
- package/dist/server/system-graph.js +40 -0
- package/dist/server/system-graph.js.map +1 -0
- package/dist/server/workflow-graph.d.ts +43 -0
- package/dist/server/workflow-graph.d.ts.map +1 -0
- package/dist/server/workflow-graph.js +200 -0
- package/dist/server/workflow-graph.js.map +1 -0
- package/dist/shared/system-graph.d.ts +59 -0
- package/dist/shared/system-graph.d.ts.map +1 -0
- package/dist/shared/system-graph.js +76 -0
- package/dist/shared/system-graph.js.map +1 -0
- package/dist/shared/types.d.ts +45 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/web/assets/index-BLrCZ-6M.css +32 -0
- package/dist/web/assets/index-CzCPIbq9.js +513 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/index-B18B2y5o.js +0 -494
- package/dist/web/assets/index-jGBbXx9R.css +0 -32
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /api/agents/move` — the Project axis's drag, performed on disk
|
|
3
|
+
* (SAP-2930; design.md § Drag semantics, criteria 15–17).
|
|
4
|
+
*
|
|
5
|
+
* TWO GUARDS, NOT ONE. The rail asks `web/src/lib/agent-move.ts`'s `planMove`
|
|
6
|
+
* before it dispatches, and that planner produces the reason a row shows. This
|
|
7
|
+
* route is not a second opinion, it is the LAST LINE: in the reference
|
|
8
|
+
* prototype the mover rewrote paths unconditionally, so anything that reached
|
|
9
|
+
* it around the rail — a macro, a test, a future keyboard move — got no check
|
|
10
|
+
* at all and clobbered silently. A planner is not a permission system, so this
|
|
11
|
+
* route STATS the destination itself and refuses on its own findings. The
|
|
12
|
+
* co-located test calls it directly, planner bypassed, to prove that.
|
|
13
|
+
*
|
|
14
|
+
* The planner also cannot see what a path list cannot see: a collision with a
|
|
15
|
+
* plain directory that holds no agent. Only a real `stat` answers that, and it
|
|
16
|
+
* happens here.
|
|
17
|
+
*
|
|
18
|
+
* `git mv` WHEN THE DIRECTORY IS TRACKED, plain rename otherwise (the settled
|
|
19
|
+
* decision on SAP-2930). A tracked move that goes through `rename` reaches git
|
|
20
|
+
* as a delete plus a pile of untracked files, which is a worse diff and a
|
|
21
|
+
* worse `git status` than the rename git can record itself. Nothing inside
|
|
22
|
+
* `sapiom.json` is rewritten: nothing in it names the agent's own directory,
|
|
23
|
+
* so a rewrite would be a second, silent edit the user did not ask for — and
|
|
24
|
+
* the registry re-derives the agent from its new path anyway.
|
|
25
|
+
*
|
|
26
|
+
* Authority: NEITHER SIDE OF THE MOVE IS A STRING FROM THE REQUEST. `from` is
|
|
27
|
+
* matched against the live registry (`resolveAgent`, injected — the same cache
|
|
28
|
+
* `actions.ts` and the canvas route resolve through) and the move runs on
|
|
29
|
+
* `agent.path`, which the registry wrote when it scanned the disk. `to` is
|
|
30
|
+
* matched against `listMoveTargetDirs` — every project root the studio knows
|
|
31
|
+
* plus every branching directory the Project axis draws between a root and an
|
|
32
|
+
* agent — and the move runs on `<that directory>/<the registry's basename>`.
|
|
33
|
+
*
|
|
34
|
+
* That is the same shape `server/studio-rail.ts` uses for its writable roots,
|
|
35
|
+
* and it is deliberately stronger than validating the client's path: a
|
|
36
|
+
* destination the rail cannot show is a destination this route will not `mv`
|
|
37
|
+
* to, so the endpoint cannot be turned into an arbitrary-path `mv` even by a
|
|
38
|
+
* caller that never opened the rail. It also means no request string reaches
|
|
39
|
+
* `fs`, which is a barrier a static analyzer can see and a reordered `if`
|
|
40
|
+
* cannot undo. The lexical guards (absolute-only, no `..` segment) stay as
|
|
41
|
+
* defence in depth.
|
|
42
|
+
*
|
|
43
|
+
* Mounted under the same `/api` boot-token middleware as the rest of the REST
|
|
44
|
+
* surface (server/index.ts).
|
|
45
|
+
*/
|
|
46
|
+
import { execFile } from "node:child_process";
|
|
47
|
+
import * as fs from "node:fs/promises";
|
|
48
|
+
import * as path from "node:path";
|
|
49
|
+
import { Router } from "express";
|
|
50
|
+
import { hasTraversalSegment } from "../core/path-safety.js";
|
|
51
|
+
/** Whether `child` IS `parent` or sits beneath it, on segment boundaries — so
|
|
52
|
+
* `…/ads-v2` is never read as a child of `…/ads`. Node-side twin of the
|
|
53
|
+
* browser's `paths.isWithinDir`; `path.relative` does the normalizing. */
|
|
54
|
+
export function isWithinDir(parent, child) {
|
|
55
|
+
const rel = path.relative(path.resolve(parent), path.resolve(child));
|
|
56
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Two paths naming one directory. Trailing separators and (on Windows) case are
|
|
60
|
+
* spelling, not identity — the same rule `server/studio-rail.ts` applies to its
|
|
61
|
+
* roots, and for the same reason: the client stores whichever form the user
|
|
62
|
+
* typed while the server stores whatever it resolved.
|
|
63
|
+
*/
|
|
64
|
+
function samePath(a, b) {
|
|
65
|
+
const norm = (p) => {
|
|
66
|
+
const resolved = path.resolve(p);
|
|
67
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
68
|
+
};
|
|
69
|
+
return norm(a) === norm(b);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Every directory the Project axis can offer as a drop target, derived from the
|
|
73
|
+
* only two things the server holds: the roots the studio knows about and the
|
|
74
|
+
* agents the registry has found.
|
|
75
|
+
*
|
|
76
|
+
* This mirrors `web/src/lib/project-tree.ts` exactly, because it has to. That
|
|
77
|
+
* module tries each root, keeps the agents beneath it, and renders the path
|
|
78
|
+
* segments between them as directory rows; a row that exists there is a row a
|
|
79
|
+
* drag can land on, and nothing else is. So: each root, plus each directory
|
|
80
|
+
* between a root and an agent inside it.
|
|
81
|
+
*
|
|
82
|
+
* KNOWN GAP, deliberately: the rail also shows `pendingCwds` — a folder chosen
|
|
83
|
+
* for an agent that is still being created — and the server has no such list.
|
|
84
|
+
* Dropping into a project that exists only mid-creation is refused with the
|
|
85
|
+
* "directory the studio doesn't show" message until the folder lands in
|
|
86
|
+
* `recentDirs`, which is the next thing that happens to it. Refusing a rare
|
|
87
|
+
* drop is the right side to fail on for a route that renames user code.
|
|
88
|
+
*/
|
|
89
|
+
export function moveTargetDirs(roots, agentPaths) {
|
|
90
|
+
const resolvedRoots = roots
|
|
91
|
+
.filter((root) => typeof root === "string" && root.trim() !== "")
|
|
92
|
+
.map((root) => path.resolve(root));
|
|
93
|
+
const dirs = new Set(resolvedRoots);
|
|
94
|
+
for (const raw of agentPaths) {
|
|
95
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
96
|
+
continue;
|
|
97
|
+
const agent = path.resolve(raw);
|
|
98
|
+
for (const root of resolvedRoots) {
|
|
99
|
+
if (!isWithinDir(root, agent))
|
|
100
|
+
continue;
|
|
101
|
+
// Every segment between the root and the agent's own folder. The agent's
|
|
102
|
+
// folder itself is NOT a target: an agent row is not a drop target, and
|
|
103
|
+
// `mv a a/b` is the one move that destroys the thing being moved.
|
|
104
|
+
let dir = path.dirname(agent);
|
|
105
|
+
while (isWithinDir(root, dir)) {
|
|
106
|
+
dirs.add(dir);
|
|
107
|
+
const parent = path.dirname(dir);
|
|
108
|
+
if (parent === dir)
|
|
109
|
+
break;
|
|
110
|
+
dir = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return [...dirs];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The path `p` becomes once `from` has moved to `to` — unchanged when `p` is
|
|
118
|
+
* not inside `from`.
|
|
119
|
+
*
|
|
120
|
+
* Everything under the moved directory travels with it, so a nested agent and a
|
|
121
|
+
* SESSION whose cwd sat inside the tree are remapped by the same rule. Without
|
|
122
|
+
* this a session keeps pointing at a directory that no longer exists.
|
|
123
|
+
*/
|
|
124
|
+
export function remapUnder(p, from, to) {
|
|
125
|
+
if (!isWithinDir(from, p))
|
|
126
|
+
return p;
|
|
127
|
+
const rel = path.relative(path.resolve(from), path.resolve(p));
|
|
128
|
+
return rel === "" ? path.resolve(to) : path.join(path.resolve(to), rel);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Rewrites every live session that sat inside the moved tree, IN PLACE, and
|
|
132
|
+
* returns how many changed.
|
|
133
|
+
*
|
|
134
|
+
* A function rather than a loop at the wiring site because this is the criterion
|
|
135
|
+
* "sessions whose cwd sat inside the moved directory follow it", and a rule that
|
|
136
|
+
* lives inside a route's callback cannot be tested. On disk the session's own
|
|
137
|
+
* process cwd follows the directory automatically (a cwd is an inode, not a
|
|
138
|
+
* string), so what has to be corrected is the RECORD — the path the studio shows,
|
|
139
|
+
* files under, and reboots the session at. Left stale, the session's project row
|
|
140
|
+
* points at a directory that no longer exists.
|
|
141
|
+
*
|
|
142
|
+
* `boundWorkflowPath` travels the same way: the binding names the agent's
|
|
143
|
+
* directory, and the agent just moved.
|
|
144
|
+
*
|
|
145
|
+
* In place, because the session manager owns these objects and hands out live
|
|
146
|
+
* references — `setBoundWorkflowPath` mutates them the same way.
|
|
147
|
+
*/
|
|
148
|
+
export function remapSessions(sessions, from, to) {
|
|
149
|
+
let changed = 0;
|
|
150
|
+
for (const session of sessions) {
|
|
151
|
+
const cwd = remapUnder(session.cwd, from, to);
|
|
152
|
+
const bound = session.boundWorkflowPath == null
|
|
153
|
+
? session.boundWorkflowPath
|
|
154
|
+
: remapUnder(session.boundWorkflowPath, from, to);
|
|
155
|
+
if (cwd === session.cwd && bound === session.boundWorkflowPath)
|
|
156
|
+
continue;
|
|
157
|
+
session.cwd = cwd;
|
|
158
|
+
session.boundWorkflowPath = bound;
|
|
159
|
+
changed += 1;
|
|
160
|
+
}
|
|
161
|
+
return changed;
|
|
162
|
+
}
|
|
163
|
+
/** `git`, with a bounded timeout — a hung git must not hold a request open. */
|
|
164
|
+
function git(args) {
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
execFile("git", args, { timeout: 10000, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => resolve({ ok: err == null, stdout: stdout.toString() }));
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether git TRACKS anything inside `dir`.
|
|
171
|
+
*
|
|
172
|
+
* `ls-files` rather than `rev-parse --is-inside-work-tree`: a directory can sit
|
|
173
|
+
* inside a repo and be entirely gitignored (a scratch agent under a checkout's
|
|
174
|
+
* `tmp/`), and `git mv` refuses those. "Inside a repo" is the wrong question;
|
|
175
|
+
* "does git know about these files" is the one that predicts whether `git mv`
|
|
176
|
+
* can do the job. No repo at all, or git not installed, reads as untracked.
|
|
177
|
+
*/
|
|
178
|
+
export async function isGitTracked(dir) {
|
|
179
|
+
const res = await git(["-C", dir, "ls-files", "--", "."]);
|
|
180
|
+
return res.ok && res.stdout.trim() !== "";
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Moves the directory, preferring the tracked path.
|
|
184
|
+
*
|
|
185
|
+
* A `git mv` failure falls THROUGH to the plain rename rather than failing the
|
|
186
|
+
* request: the commonest reason is a destination outside this work tree (moving
|
|
187
|
+
* an agent out of a checkout is a legitimate move), and the untracked branch
|
|
188
|
+
* would have done exactly this anyway. What must never happen is a move that
|
|
189
|
+
* half-succeeds, and neither branch can do that — both are single operations.
|
|
190
|
+
*/
|
|
191
|
+
export async function performMove(from, to) {
|
|
192
|
+
if (await isGitTracked(from)) {
|
|
193
|
+
const res = await git(["-C", from, "mv", "--", from, to]);
|
|
194
|
+
if (res.ok)
|
|
195
|
+
return "git";
|
|
196
|
+
}
|
|
197
|
+
await fs.rename(from, to);
|
|
198
|
+
return "rename";
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The route's OWN refusal, from the filesystem rather than from a path list —
|
|
202
|
+
* a message when the move must not happen, null when it may.
|
|
203
|
+
*
|
|
204
|
+
* Ordered so the most specific truth wins: a destination inside the source is
|
|
205
|
+
* the move that destroys the thing being moved (`mv a a/b` relocates the
|
|
206
|
+
* destination along with the source and leaves nothing behind), and it is worth
|
|
207
|
+
* saying so rather than reporting whatever `stat` happens to find there.
|
|
208
|
+
*/
|
|
209
|
+
export async function refuseMoveOnDisk(from, to) {
|
|
210
|
+
const name = path.basename(to);
|
|
211
|
+
if (isWithinDir(from, to))
|
|
212
|
+
return `Can't move ${path.basename(from)} inside itself.`;
|
|
213
|
+
let sourceStat;
|
|
214
|
+
try {
|
|
215
|
+
sourceStat = await fs.lstat(from);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return `${from} no longer exists.`;
|
|
219
|
+
}
|
|
220
|
+
if (!sourceStat.isDirectory())
|
|
221
|
+
return `${from} is not a directory.`;
|
|
222
|
+
// THE INDEPENDENT GUARD. `lstat`, not `stat`: a dangling symlink at the
|
|
223
|
+
// destination is still something `rename` would replace, and something the
|
|
224
|
+
// user put there.
|
|
225
|
+
try {
|
|
226
|
+
await fs.lstat(to);
|
|
227
|
+
return `${to} already exists. Moving ${name} there would overwrite it.`;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Absent — which is the only acceptable state for a destination.
|
|
231
|
+
}
|
|
232
|
+
const parent = path.dirname(to);
|
|
233
|
+
try {
|
|
234
|
+
const parentStat = await fs.stat(parent);
|
|
235
|
+
if (!parentStat.isDirectory())
|
|
236
|
+
return `${parent} is not a directory.`;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return `${parent} does not exist.`;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* POST /api/agents/move { from, to } -> AgentMoveResponse
|
|
245
|
+
*
|
|
246
|
+
* 400 — a malformed or unregistered `from`, or a `to` that asks for a rename.
|
|
247
|
+
* 409 — a refusal, with the reason in `error` so the rail can show it verbatim:
|
|
248
|
+
* a destination inside the source, a destination the studio doesn't show, or
|
|
249
|
+
* anything `stat` finds already sitting there. 200 with `moved: false` — `to`
|
|
250
|
+
* resolved to `from`: the user let go somewhere harmless, and the SILENT no-op
|
|
251
|
+
* is a success, not a complaint.
|
|
252
|
+
*/
|
|
253
|
+
export function createAgentMoveRouter(deps) {
|
|
254
|
+
const router = Router();
|
|
255
|
+
router.post("/api/agents/move", async (req, res, next) => {
|
|
256
|
+
const body = (req.body ?? {});
|
|
257
|
+
const { from, to } = body;
|
|
258
|
+
if (typeof from !== "string" || typeof to !== "string") {
|
|
259
|
+
res.status(400).json({ error: "from and to must be absolute paths" });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (!path.isAbsolute(from) ||
|
|
263
|
+
!path.isAbsolute(to) ||
|
|
264
|
+
hasTraversalSegment(from) ||
|
|
265
|
+
hasTraversalSegment(to)) {
|
|
266
|
+
res.status(400).json({ error: "from and to must be absolute paths" });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const requestedTarget = path.resolve(to);
|
|
270
|
+
// The `from` lookup, and the last use of that string: everything below
|
|
271
|
+
// moves `agent.path`, which the registry wrote when it scanned the disk.
|
|
272
|
+
const agent = deps.resolveAgent(path.resolve(from));
|
|
273
|
+
if (agent == null) {
|
|
274
|
+
res.status(400).json({ error: "from must be a registered agent" });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const source = path.resolve(agent.path);
|
|
278
|
+
const name = path.basename(source);
|
|
279
|
+
if (samePath(source, requestedTarget)) {
|
|
280
|
+
res.json({
|
|
281
|
+
ok: true,
|
|
282
|
+
moved: false,
|
|
283
|
+
from: source,
|
|
284
|
+
to: source,
|
|
285
|
+
kind: null,
|
|
286
|
+
});
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// Answered here, ahead of the destination lookup, because it is the most
|
|
290
|
+
// specific truth about the gesture and no `stat` can improve on it: `mv a
|
|
291
|
+
// a/b` relocates the destination along with the source and leaves nothing
|
|
292
|
+
// behind. Pure path arithmetic — nothing reaches the filesystem yet.
|
|
293
|
+
if (isWithinDir(source, requestedTarget)) {
|
|
294
|
+
res.status(409).json({ error: `Can't move ${name} inside itself.` });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
// THE DESTINATION BARRIER. The requested parent is matched against the
|
|
299
|
+
// directories the rail can show, and the DIRECTORY FROM THAT LIST is what
|
|
300
|
+
// the move uses — the request's spelling is discarded with the rest of
|
|
301
|
+
// the string. A destination the studio has never been pointed at is a
|
|
302
|
+
// destination this route will not rename user code into.
|
|
303
|
+
const requestedParent = path.dirname(requestedTarget);
|
|
304
|
+
const targetDir = (await deps.listMoveTargetDirs()).find((dir) => typeof dir === "string" && dir.trim() !== "" && samePath(dir, requestedParent));
|
|
305
|
+
if (targetDir == null) {
|
|
306
|
+
res.status(409).json({
|
|
307
|
+
error: `Can't move ${name} into ${requestedParent} — Studio doesn't show that folder as a project.`,
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// A move keeps the agent's own folder name; `planMove` builds `to` that
|
|
312
|
+
// way and nothing else may ask for a different one. Refusing here rather
|
|
313
|
+
// than silently moving to `<targetDir>/<name>` keeps the endpoint honest
|
|
314
|
+
// about the one thing it does.
|
|
315
|
+
if (path.basename(requestedTarget) !== name) {
|
|
316
|
+
res.status(400).json({ error: "a move may not rename the agent's directory" });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
// `path.resolve` on the LIST's entry, not the request's: an injected
|
|
320
|
+
// list may spell a directory however its source stored it.
|
|
321
|
+
const target = path.join(path.resolve(targetDir), name);
|
|
322
|
+
const refusal = await refuseMoveOnDisk(source, target);
|
|
323
|
+
if (refusal != null) {
|
|
324
|
+
res.status(409).json({ error: refusal });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const kind = await performMove(source, target);
|
|
328
|
+
await deps.onMoved(source, target);
|
|
329
|
+
res.json({
|
|
330
|
+
ok: true,
|
|
331
|
+
moved: true,
|
|
332
|
+
from: source,
|
|
333
|
+
to: target,
|
|
334
|
+
kind,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
next(err);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return router;
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=agent-move.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-move.js","sourceRoot":"","sources":["../../src/server/agent-move.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,KAAK,EAEV,QAAQ,EACR,cAAc,EACd,WAAW,EAKZ,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,KAAK,EAEV,QAAQ,EACR,cAAc,EACd,WAAW,EAKZ,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EACL,cAAc,EACd,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAC;AAwCpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,8BAA8B,CAAC;AA4GtC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;IACxD;;;;;;;wDAOoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC;;;;;yCAKqC;IACrC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC;;0EAEsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;6CAEyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;+EAE2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;gFAE4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;yEAEqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;uDAMmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;mFAE+E;IAC/E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;gEAG4D;IAC5D,kBAAkB,CAAC,EAAE,WAAW,CAAC;IACjC;;6DAEyD;IACzD,kBAAkB,CAAC,EAAE,WAAW,EAAE,CAAC;IACnC;;+EAE2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,cAAc,CAAC;CAChC;AAmLD,eAAO,MAAM,WAAW,GACtB,SAAS,oBAAoB,KAC5B,OAAO,CAAC,aAAa,CAymDvB,CAAC"}
|