@asermax/pi-tentacles 1.0.0 → 1.1.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/extension/index.js +35 -5
- package/package.json +1 -1
- package/skills/tentacles/SKILL.md +9 -1
- package/tentacles-helper +555 -251
package/extension/index.js
CHANGED
|
@@ -17105,6 +17105,32 @@ var http = (options) => Object.assign(Object.create(Proto), {
|
|
|
17105
17105
|
* @since 4.0.0
|
|
17106
17106
|
*/
|
|
17107
17107
|
var bearer = /*#__PURE__*/ http({ scheme: "Bearer" });
|
|
17108
|
+
// ../../packages/worker-api/src/archive-kinds.ts
|
|
17109
|
+
/**
|
|
17110
|
+
* The kinds of archive a create request carries. `expandsInto` is where the workspace container
|
|
17111
|
+
* expands the kind's archive; a null one is read by the Worker only and never reaches the container.
|
|
17112
|
+
*/
|
|
17113
|
+
var archiveKinds = {
|
|
17114
|
+
skills: {
|
|
17115
|
+
heading: "Skills",
|
|
17116
|
+
expandsInto: "/skills"
|
|
17117
|
+
},
|
|
17118
|
+
agents: {
|
|
17119
|
+
heading: "Agents",
|
|
17120
|
+
expandsInto: null
|
|
17121
|
+
},
|
|
17122
|
+
context: {
|
|
17123
|
+
heading: "Context",
|
|
17124
|
+
expandsInto: "/context"
|
|
17125
|
+
}
|
|
17126
|
+
};
|
|
17127
|
+
keys(archiveKinds).flatMap((kind) => {
|
|
17128
|
+
const { expandsInto } = archiveKinds[kind];
|
|
17129
|
+
return expandsInto == null ? [] : [{
|
|
17130
|
+
kind,
|
|
17131
|
+
directory: expandsInto
|
|
17132
|
+
}];
|
|
17133
|
+
});
|
|
17108
17134
|
// ../../packages/worker-api/src/middleware.ts
|
|
17109
17135
|
/** Every request carries the API key as a bearer token; a provider checks it against its secret. */
|
|
17110
17136
|
var ApiKey = class extends Service()("tentacles/ApiKey", {
|
|
@@ -17121,8 +17147,7 @@ var CreateSession = Struct({
|
|
|
17121
17147
|
parent: Parent,
|
|
17122
17148
|
settings: Settings,
|
|
17123
17149
|
prompt: String$1,
|
|
17124
|
-
|
|
17125
|
-
agents: optionalKey(Archive)
|
|
17150
|
+
...map$5(archiveKinds, () => optionalKey(Archive))
|
|
17126
17151
|
});
|
|
17127
17152
|
var Prompt = Struct({
|
|
17128
17153
|
text: String$1,
|
|
@@ -17209,10 +17234,15 @@ var endpointArguments = (name) => {
|
|
|
17209
17234
|
};
|
|
17210
17235
|
/** What the agent sends to spawn: the glue fills the parent. */
|
|
17211
17236
|
var SpawnArguments = CreateSession.mapFields((fields) => ({
|
|
17212
|
-
...omit(fields, [
|
|
17237
|
+
...omit(fields, [
|
|
17238
|
+
"skills",
|
|
17239
|
+
"agents",
|
|
17240
|
+
"context"
|
|
17241
|
+
]),
|
|
17213
17242
|
settings: optionalKey(Settings.mapFields(map(optionalKey))),
|
|
17214
17243
|
skills: optionalKey(ArraySchema(String$1).annotate({ description: "Paths of skill folders on this machine, each holding a SKILL.md." })),
|
|
17215
|
-
agents: optionalKey(ArraySchema(String$1).annotate({ description: "Paths of agent definition files on this machine." }))
|
|
17244
|
+
agents: optionalKey(ArraySchema(String$1).annotate({ description: "Paths of agent definition files on this machine." })),
|
|
17245
|
+
context: optionalKey(ArraySchema(String$1).annotate({ description: "Paths of files or folders on this machine; the remote agent finds them in /context, each under its own name." }))
|
|
17216
17246
|
})).mapFields(omit(["parent"]));
|
|
17217
17247
|
// src/core/tool-table.ts
|
|
17218
17248
|
var ToolNames = {
|
|
@@ -17231,7 +17261,7 @@ var spawnDocument = toJsonSchemaDocument(SpawnArguments);
|
|
|
17231
17261
|
var ToolTable = {
|
|
17232
17262
|
spawn: ToolEntry.Endpoint({
|
|
17233
17263
|
name: ToolNames.spawn,
|
|
17234
|
-
description: "Start a remote agent with a name, a first prompt and optional settings. The settings left out come from the plugin's defaults. Optionally takes the paths of skill folders and agent definition files, which stay fixed for the session. Answers with the new session's id.",
|
|
17264
|
+
description: "Start a remote agent with a name, a first prompt and optional settings. The settings left out come from the plugin's defaults. Optionally takes the paths of skill folders and agent definition files, and the paths of context files or folders the remote agent finds in /context, which stay fixed for the session. Answers with the new session's id.",
|
|
17235
17265
|
endpoint: "create",
|
|
17236
17266
|
harnesses: bothHarnesses,
|
|
17237
17267
|
arguments: {
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ A remote agent only keeps what it commits and pushes. Its workspace is disposabl
|
|
|
16
16
|
|
|
17
17
|
## Spawn
|
|
18
18
|
|
|
19
|
-
`spawn` takes a name, the first prompt, the session's settings and, optionally, skills
|
|
19
|
+
`spawn` takes a name, the first prompt, the session's settings and, optionally, skills, agent definitions and context files (see Skills and agents, and Context files). The name shows in your status line, so name it after the agent's task. The settings you leave out come from the plugin's defaults.
|
|
20
20
|
|
|
21
21
|
Write the first prompt for someone who has not seen this conversation: the goal, the repository, the branch to push, and what done looks like. Name the repository in the settings when the agent has to work in it, so tentacles clones the repository and, before the agent asks you anything, checks it for work that is not pushed.
|
|
22
22
|
|
|
@@ -30,6 +30,14 @@ Every definition is checked on your machine before anything is sent. When any fa
|
|
|
30
30
|
|
|
31
31
|
A remote agent works on its own, so a skill that would stop to ask the person has nobody to ask. Write the handoff so the remote agent decides wherever a skill would stop for the user, ignores any instruction in a skill to ask the user, and lists every such decision in its final report. For a stop point it cannot decide, such as a credential it lacks, say in the handoff what to do, which can be asking you with its question tool.
|
|
32
32
|
|
|
33
|
+
## Context files
|
|
34
|
+
|
|
35
|
+
`spawn` takes `context`, the paths of files and folders on your machine that the job needs and the repository does not hold, such as a change's scratch files. Each path lands in the remote agent's `/context` folder under its own name: a file at `/context/<file name>`, a folder at `/context/<folder name>/…`. `/context` is outside the repository, so the files never reach its commits. A folder takes everything under it, a symlink is sent as the file or folder it points to, and every file keeps its permission bits.
|
|
36
|
+
|
|
37
|
+
The remote agent has a copy that is fixed at spawn. Its edits to the copy are lost when its workspace restarts and never come back to you, so spawn a new remote agent when the job needs different files. The remote agent is told only that you sent files and that they are in `/context`, so say in the handoff what the files are and how to use them.
|
|
38
|
+
|
|
39
|
+
The paths are checked on your machine before anything is sent. A path that does not exist or cannot be read, two paths with the same name, or files that pack over 5 MB make `spawn` create nothing and return every problem with its path in a Context group, beside the Skills and Agents groups.
|
|
40
|
+
|
|
33
41
|
## Being woken
|
|
34
42
|
|
|
35
43
|
You are woken when a remote agent stops. The wake carries the agent's last message when it finishes, the reason when it fails, and the question when it asks one. You never poll. If you think a wake was missed, run `list`.
|
package/tentacles-helper
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import * as fsp from "node:fs/promises";
|
|
4
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { basename, join, resolve } from "node:path";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
8
|
+
import { createGzip } from "node:zlib";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import { cpus } from "node:os";
|
|
11
|
+
import { Readable } from "node:stream";
|
|
5
12
|
// \0rolldown/runtime.js
|
|
6
13
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
|
|
7
14
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
@@ -29256,6 +29263,34 @@ var http = (options) => Object.assign(Object.create(Proto$2), {
|
|
|
29256
29263
|
* @since 4.0.0
|
|
29257
29264
|
*/
|
|
29258
29265
|
var bearer = /*#__PURE__*/ http({ scheme: "Bearer" });
|
|
29266
|
+
// ../../packages/worker-api/src/archive-kinds.ts
|
|
29267
|
+
/**
|
|
29268
|
+
* The kinds of archive a create request carries. `expandsInto` is where the workspace container
|
|
29269
|
+
* expands the kind's archive; a null one is read by the Worker only and never reaches the container.
|
|
29270
|
+
*/
|
|
29271
|
+
var archiveKinds = {
|
|
29272
|
+
skills: {
|
|
29273
|
+
heading: "Skills",
|
|
29274
|
+
expandsInto: "/skills"
|
|
29275
|
+
},
|
|
29276
|
+
agents: {
|
|
29277
|
+
heading: "Agents",
|
|
29278
|
+
expandsInto: null
|
|
29279
|
+
},
|
|
29280
|
+
context: {
|
|
29281
|
+
heading: "Context",
|
|
29282
|
+
expandsInto: "/context"
|
|
29283
|
+
}
|
|
29284
|
+
};
|
|
29285
|
+
/** The kinds in table order. */
|
|
29286
|
+
var archiveKindNames = keys(archiveKinds);
|
|
29287
|
+
archiveKindNames.flatMap((kind) => {
|
|
29288
|
+
const { expandsInto } = archiveKinds[kind];
|
|
29289
|
+
return expandsInto == null ? [] : [{
|
|
29290
|
+
kind,
|
|
29291
|
+
directory: expandsInto
|
|
29292
|
+
}];
|
|
29293
|
+
});
|
|
29259
29294
|
// ../../packages/worker-api/src/middleware.ts
|
|
29260
29295
|
/** Every request carries the API key as a bearer token; a provider checks it against its secret. */
|
|
29261
29296
|
var ApiKey = class extends Service()("tentacles/ApiKey", {
|
|
@@ -29280,8 +29315,7 @@ var CreateSession = Struct({
|
|
|
29280
29315
|
parent: Parent,
|
|
29281
29316
|
settings: Settings,
|
|
29282
29317
|
prompt: String$2,
|
|
29283
|
-
|
|
29284
|
-
agents: optionalKey(Archive)
|
|
29318
|
+
...map$8(archiveKinds, () => optionalKey(Archive))
|
|
29285
29319
|
});
|
|
29286
29320
|
var Prompt = Struct({
|
|
29287
29321
|
text: String$2,
|
|
@@ -30476,16 +30510,25 @@ var exhaustive = exhaustive$1;
|
|
|
30476
30510
|
/**
|
|
30477
30511
|
* What the helper receives for spawn: CreateSession with every setting
|
|
30478
30512
|
* optional, since the helper fills the rest from its variables, and with the
|
|
30479
|
-
* paths of the skills and
|
|
30513
|
+
* paths of the skills, agents and context in place of their archives, which the helper packs.
|
|
30480
30514
|
*/
|
|
30481
30515
|
var SpawnRequest = CreateSession.mapFields((fields) => ({
|
|
30482
|
-
...omit(fields, [
|
|
30516
|
+
...omit(fields, [
|
|
30517
|
+
"skills",
|
|
30518
|
+
"agents",
|
|
30519
|
+
"context"
|
|
30520
|
+
]),
|
|
30483
30521
|
settings: optionalKey(Settings.mapFields(map$3(optionalKey))),
|
|
30484
30522
|
skills: optionalKey(ArraySchema(String$2).annotate({ description: "Paths of skill folders on this machine, each holding a SKILL.md." })),
|
|
30485
|
-
agents: optionalKey(ArraySchema(String$2).annotate({ description: "Paths of agent definition files on this machine." }))
|
|
30523
|
+
agents: optionalKey(ArraySchema(String$2).annotate({ description: "Paths of agent definition files on this machine." })),
|
|
30524
|
+
context: optionalKey(ArraySchema(String$2).annotate({ description: "Paths of files or folders on this machine; the remote agent finds them in /context, each under its own name." }))
|
|
30486
30525
|
}));
|
|
30487
30526
|
SpawnRequest.mapFields(omit(["parent"]));
|
|
30488
30527
|
// ../../node_modules/.pnpm/modern-tar@0.8.6/node_modules/modern-tar/dist/packer-By1HVV7Z.js
|
|
30528
|
+
var FILE = "file";
|
|
30529
|
+
var LINK = "link";
|
|
30530
|
+
var SYMLINK = "symlink";
|
|
30531
|
+
var DIRECTORY = "directory";
|
|
30489
30532
|
var TYPEFLAG = {
|
|
30490
30533
|
file: "0",
|
|
30491
30534
|
link: "1",
|
|
@@ -30645,7 +30688,7 @@ function getHeaderBlocks(header) {
|
|
|
30645
30688
|
];
|
|
30646
30689
|
}
|
|
30647
30690
|
var EOF_BUFFER = /* @__PURE__ */ new Uint8Array(1024);
|
|
30648
|
-
function createTarPacker
|
|
30691
|
+
function createTarPacker(onData) {
|
|
30649
30692
|
let currentHeader = null;
|
|
30650
30693
|
let bytesWritten = 0;
|
|
30651
30694
|
let finalized = false;
|
|
@@ -30694,171 +30737,6 @@ function createTarPacker$1(onData) {
|
|
|
30694
30737
|
}
|
|
30695
30738
|
};
|
|
30696
30739
|
}
|
|
30697
|
-
// ../../node_modules/.pnpm/modern-tar@0.8.6/node_modules/modern-tar/dist/web/index.js
|
|
30698
|
-
function createTarPacker() {
|
|
30699
|
-
let streamController;
|
|
30700
|
-
let packer;
|
|
30701
|
-
let bodyController = null;
|
|
30702
|
-
let resume = null;
|
|
30703
|
-
let drain = null;
|
|
30704
|
-
let stopped = false;
|
|
30705
|
-
let stopReason;
|
|
30706
|
-
const unblock = () => {
|
|
30707
|
-
resume?.();
|
|
30708
|
-
resume = null;
|
|
30709
|
-
drain = null;
|
|
30710
|
-
};
|
|
30711
|
-
const stop = (reason) => {
|
|
30712
|
-
if (stopped) return;
|
|
30713
|
-
stopped = true;
|
|
30714
|
-
stopReason = reason;
|
|
30715
|
-
bodyController?.error(reason);
|
|
30716
|
-
bodyController = null;
|
|
30717
|
-
streamController.error(reason);
|
|
30718
|
-
unblock();
|
|
30719
|
-
};
|
|
30720
|
-
const abortBody = (reason = AbortSignal.abort().reason) => {
|
|
30721
|
-
bodyController = null;
|
|
30722
|
-
stop(reason);
|
|
30723
|
-
};
|
|
30724
|
-
const checkStopped = () => {
|
|
30725
|
-
if (stopped) throw stopReason;
|
|
30726
|
-
};
|
|
30727
|
-
const emit = (operation) => {
|
|
30728
|
-
checkStopped();
|
|
30729
|
-
try {
|
|
30730
|
-
operation();
|
|
30731
|
-
} catch (error) {
|
|
30732
|
-
stop(error);
|
|
30733
|
-
throw error;
|
|
30734
|
-
}
|
|
30735
|
-
const pending = drain;
|
|
30736
|
-
if (!pending) return;
|
|
30737
|
-
const signal = bodyController?.signal;
|
|
30738
|
-
if (!signal) return;
|
|
30739
|
-
if (signal.aborted) abortBody(signal.reason);
|
|
30740
|
-
else signal.onabort = () => abortBody(signal.reason);
|
|
30741
|
-
return pending.then(checkStopped);
|
|
30742
|
-
};
|
|
30743
|
-
return {
|
|
30744
|
-
readable: new ReadableStream({
|
|
30745
|
-
start(controller) {
|
|
30746
|
-
streamController = controller;
|
|
30747
|
-
packer = createTarPacker$1((chunk) => {
|
|
30748
|
-
const buffer = chunk.buffer;
|
|
30749
|
-
controller.enqueue(buffer instanceof ArrayBuffer && !Reflect.get(buffer, "resizable") ? chunk : new Uint8Array(chunk));
|
|
30750
|
-
if ((controller.desiredSize ?? 1) <= 0) drain ||= new Promise((resolve) => {
|
|
30751
|
-
resume = resolve;
|
|
30752
|
-
});
|
|
30753
|
-
});
|
|
30754
|
-
},
|
|
30755
|
-
pull: unblock,
|
|
30756
|
-
cancel(reason = AbortSignal.abort().reason) {
|
|
30757
|
-
stop(reason);
|
|
30758
|
-
}
|
|
30759
|
-
}, new ByteLengthQueuingStrategy({ highWaterMark: 8388608 })),
|
|
30760
|
-
controller: {
|
|
30761
|
-
add(header) {
|
|
30762
|
-
try {
|
|
30763
|
-
checkStopped();
|
|
30764
|
-
if (bodyController) throw new Error("Previous entry must be completed before adding a new one");
|
|
30765
|
-
packer.add(header);
|
|
30766
|
-
} catch (error) {
|
|
30767
|
-
stop(error);
|
|
30768
|
-
throw error;
|
|
30769
|
-
}
|
|
30770
|
-
return new WritableStream({
|
|
30771
|
-
start(controller) {
|
|
30772
|
-
bodyController = controller;
|
|
30773
|
-
},
|
|
30774
|
-
write(chunk) {
|
|
30775
|
-
return emit(() => {
|
|
30776
|
-
if (!ArrayBuffer.isView(chunk) || chunk[Symbol.toStringTag] !== "Uint8Array") throw new TypeError("Expected a Uint8Array body chunk");
|
|
30777
|
-
packer.write(chunk);
|
|
30778
|
-
});
|
|
30779
|
-
},
|
|
30780
|
-
close() {
|
|
30781
|
-
const result = emit(() => packer.endEntry());
|
|
30782
|
-
if (!result) {
|
|
30783
|
-
bodyController = null;
|
|
30784
|
-
return;
|
|
30785
|
-
}
|
|
30786
|
-
return result.then(() => {
|
|
30787
|
-
bodyController = null;
|
|
30788
|
-
});
|
|
30789
|
-
},
|
|
30790
|
-
abort: abortBody
|
|
30791
|
-
});
|
|
30792
|
-
},
|
|
30793
|
-
finalize() {
|
|
30794
|
-
try {
|
|
30795
|
-
checkStopped();
|
|
30796
|
-
if (bodyController) throw new Error("Cannot finalize while an entry is still active");
|
|
30797
|
-
packer.finalize();
|
|
30798
|
-
streamController.close();
|
|
30799
|
-
} catch (error) {
|
|
30800
|
-
stop(error);
|
|
30801
|
-
throw error;
|
|
30802
|
-
}
|
|
30803
|
-
},
|
|
30804
|
-
error(err) {
|
|
30805
|
-
stop(err);
|
|
30806
|
-
}
|
|
30807
|
-
}
|
|
30808
|
-
};
|
|
30809
|
-
}
|
|
30810
|
-
async function streamToBuffer(stream) {
|
|
30811
|
-
const chunks = [];
|
|
30812
|
-
const reader = stream.getReader();
|
|
30813
|
-
let totalLength = 0;
|
|
30814
|
-
try {
|
|
30815
|
-
while (true) {
|
|
30816
|
-
const { done, value } = await reader.read();
|
|
30817
|
-
if (done) break;
|
|
30818
|
-
chunks.push(value);
|
|
30819
|
-
totalLength += value.length;
|
|
30820
|
-
}
|
|
30821
|
-
const result = new Uint8Array(totalLength);
|
|
30822
|
-
let offset = 0;
|
|
30823
|
-
for (const chunk of chunks) {
|
|
30824
|
-
result.set(chunk, offset);
|
|
30825
|
-
offset += chunk.length;
|
|
30826
|
-
}
|
|
30827
|
-
return result;
|
|
30828
|
-
} finally {
|
|
30829
|
-
reader.releaseLock();
|
|
30830
|
-
}
|
|
30831
|
-
}
|
|
30832
|
-
async function packTar(entries) {
|
|
30833
|
-
const { readable, controller } = createTarPacker();
|
|
30834
|
-
const archive = streamToBuffer(readable);
|
|
30835
|
-
try {
|
|
30836
|
-
for (const entry of entries) {
|
|
30837
|
-
const entryStream = controller.add(entry.header);
|
|
30838
|
-
const body = "body" in entry ? entry.body : entry.data;
|
|
30839
|
-
if (!body) {
|
|
30840
|
-
await entryStream.close();
|
|
30841
|
-
continue;
|
|
30842
|
-
}
|
|
30843
|
-
if (body instanceof ReadableStream) await body.pipeTo(entryStream);
|
|
30844
|
-
else if (body instanceof Blob) await body.stream().pipeTo(entryStream);
|
|
30845
|
-
else try {
|
|
30846
|
-
const chunk = await normalizeBody(body);
|
|
30847
|
-
if (chunk.length > 0) {
|
|
30848
|
-
const writer = entryStream.getWriter();
|
|
30849
|
-
await writer.write(chunk);
|
|
30850
|
-
await writer.close();
|
|
30851
|
-
} else await entryStream.close();
|
|
30852
|
-
} catch {
|
|
30853
|
-
throw new TypeError(`Unsupported content type for entry "${entry.header.name}".`);
|
|
30854
|
-
}
|
|
30855
|
-
}
|
|
30856
|
-
controller.finalize();
|
|
30857
|
-
} catch (error) {
|
|
30858
|
-
controller.error(error);
|
|
30859
|
-
}
|
|
30860
|
-
return archive;
|
|
30861
|
-
}
|
|
30862
30740
|
// ../../node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
|
|
30863
30741
|
var require_identity = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
30864
30742
|
var ALIAS = Symbol.for("yaml.alias");
|
|
@@ -37575,34 +37453,410 @@ var checkAgents = (sources) => {
|
|
|
37575
37453
|
};
|
|
37576
37454
|
TaggedError()("ArchiveUnreadable", { message: String$2 });
|
|
37577
37455
|
var skillFile = "SKILL.md";
|
|
37578
|
-
var gzip = async (bytes) => new Uint8Array(await new Response(new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer());
|
|
37579
|
-
/** Packs entries into a tar.gz, the form `readArchive` reads. */
|
|
37580
|
-
var packArchive = (entries) => promise(async () => gzip(await packTar(entries.map((entry) => ({
|
|
37581
|
-
header: {
|
|
37582
|
-
name: entry.path,
|
|
37583
|
-
size: entry.bytes.byteLength,
|
|
37584
|
-
mode: entry.mode
|
|
37585
|
-
},
|
|
37586
|
-
body: entry.bytes
|
|
37587
|
-
})))));
|
|
37588
37456
|
new TextDecoder();
|
|
37457
|
+
// ../../node_modules/.pnpm/modern-tar@0.8.6/node_modules/modern-tar/dist/fs/index.js
|
|
37458
|
+
var win32Reserved = {
|
|
37459
|
+
":": "",
|
|
37460
|
+
"<": "",
|
|
37461
|
+
">": "",
|
|
37462
|
+
"|": "",
|
|
37463
|
+
"?": "",
|
|
37464
|
+
"*": "",
|
|
37465
|
+
"\"": ""
|
|
37466
|
+
};
|
|
37467
|
+
function normalizeName(name) {
|
|
37468
|
+
const path = name.replace(/\\/g, "/");
|
|
37469
|
+
if (path.split("/").includes("..") || /^[a-zA-Z]:\.\./.test(path)) throw new Error(`${name} points outside extraction directory`);
|
|
37470
|
+
let relative = path;
|
|
37471
|
+
if (/^[a-zA-Z]:/.test(relative)) relative = relative.replace(/^[a-zA-Z]:[/\\]?/, "");
|
|
37472
|
+
else if (relative.startsWith("/")) relative = relative.replace(/^\/+/, "");
|
|
37473
|
+
if (process.platform === "win32") return relative.replace(/[<>:"|?*]/g, (char) => win32Reserved[char]);
|
|
37474
|
+
return relative;
|
|
37475
|
+
}
|
|
37476
|
+
var BIGINT_STAT = { bigint: true };
|
|
37477
|
+
var WITH_FILE_TYPES = { withFileTypes: true };
|
|
37478
|
+
function packTar(sources, options = {}) {
|
|
37479
|
+
const results = /* @__PURE__ */ new Map();
|
|
37480
|
+
const fileHandles = /* @__PURE__ */ new Map();
|
|
37481
|
+
const bodyStreams = /* @__PURE__ */ new Set();
|
|
37482
|
+
let resume = null;
|
|
37483
|
+
let drain = null;
|
|
37484
|
+
let resumeWriter = null;
|
|
37485
|
+
let cancelError;
|
|
37486
|
+
const unblock = () => {
|
|
37487
|
+
const resolve = resume;
|
|
37488
|
+
resume = null;
|
|
37489
|
+
drain = null;
|
|
37490
|
+
resolve?.();
|
|
37491
|
+
};
|
|
37492
|
+
const wakeWriter = () => {
|
|
37493
|
+
resumeWriter?.();
|
|
37494
|
+
resumeWriter = null;
|
|
37495
|
+
};
|
|
37496
|
+
const destroyBody = (body, reason) => {
|
|
37497
|
+
bodyStreams.delete(body);
|
|
37498
|
+
body.destroy(reason);
|
|
37499
|
+
};
|
|
37500
|
+
const closeHandle = (handle) => {
|
|
37501
|
+
const closing = fileHandles.get(handle);
|
|
37502
|
+
if (closing !== null) return closing;
|
|
37503
|
+
const promise = handle.close().finally(() => fileHandles.delete(handle));
|
|
37504
|
+
fileHandles.set(handle, promise);
|
|
37505
|
+
return promise;
|
|
37506
|
+
};
|
|
37507
|
+
const stop = async (reason) => {
|
|
37508
|
+
for (const body of bodyStreams) destroyBody(body, reason);
|
|
37509
|
+
const closing = Promise.allSettled([...fileHandles.keys()].map(closeHandle));
|
|
37510
|
+
results.clear();
|
|
37511
|
+
wakeWriter();
|
|
37512
|
+
for (const result of await closing) if (result.status === "rejected") throw result.reason;
|
|
37513
|
+
};
|
|
37514
|
+
const stream = new Readable({
|
|
37515
|
+
highWaterMark: 8388608,
|
|
37516
|
+
read: unblock,
|
|
37517
|
+
destroy(error, callback) {
|
|
37518
|
+
cancelError = error ?? AbortSignal.abort().reason;
|
|
37519
|
+
unblock();
|
|
37520
|
+
stop(cancelError).then(() => callback(error), (closeError) => callback(error ?? closeError));
|
|
37521
|
+
}
|
|
37522
|
+
});
|
|
37523
|
+
const onError = (error) => stream.destroy(error);
|
|
37524
|
+
const packer = createTarPacker((chunk) => {
|
|
37525
|
+
if (stream.destroyed) throw cancelError;
|
|
37526
|
+
if (!stream.push(Buffer.from(chunk)) && !drain) drain = new Promise((resolve) => {
|
|
37527
|
+
resume = resolve;
|
|
37528
|
+
});
|
|
37529
|
+
});
|
|
37530
|
+
(async () => {
|
|
37531
|
+
const { dereference = false, filter, map, baseDir, concurrency = cpus().length || 8 } = options;
|
|
37532
|
+
let directoryPath;
|
|
37533
|
+
let realBaseDir;
|
|
37534
|
+
let jobs;
|
|
37535
|
+
if (typeof sources === "string") {
|
|
37536
|
+
const source = path.resolve(sources);
|
|
37537
|
+
directoryPath = source;
|
|
37538
|
+
const before = await fsp.stat(source, BIGINT_STAT);
|
|
37539
|
+
if (stream.destroyed) return;
|
|
37540
|
+
const entries = await fsp.readdir(source, WITH_FILE_TYPES);
|
|
37541
|
+
if (stream.destroyed) return;
|
|
37542
|
+
const after = await fsp.stat(source, BIGINT_STAT);
|
|
37543
|
+
if (stream.destroyed) return;
|
|
37544
|
+
jobs = before.dev === after.dev && before.ino === after.ino ? entries.map((entry) => ({
|
|
37545
|
+
type: entry.isDirectory() ? DIRECTORY : FILE,
|
|
37546
|
+
source: path.join(source, entry.name),
|
|
37547
|
+
target: entry.name
|
|
37548
|
+
})) : [];
|
|
37549
|
+
} else jobs = sources.map((source) => ({ ...source }));
|
|
37550
|
+
const seenHardlinks = /* @__PURE__ */ new Map();
|
|
37551
|
+
let jobIndex = 0;
|
|
37552
|
+
let writeIndex = 0;
|
|
37553
|
+
let activeWorkers = 0;
|
|
37554
|
+
let allJobsQueued = false;
|
|
37555
|
+
const writeStreamBody = async (body) => {
|
|
37556
|
+
try {
|
|
37557
|
+
for await (const chunk of body) {
|
|
37558
|
+
if (stream.destroyed) return;
|
|
37559
|
+
packer.write(chunk instanceof Uint8Array ? chunk : Buffer.from(chunk));
|
|
37560
|
+
if (drain) await drain;
|
|
37561
|
+
}
|
|
37562
|
+
} finally {
|
|
37563
|
+
body.off("error", onError);
|
|
37564
|
+
bodyStreams.delete(body);
|
|
37565
|
+
}
|
|
37566
|
+
};
|
|
37567
|
+
const writer = async () => {
|
|
37568
|
+
const readBufferSmall = Buffer.alloc(65536);
|
|
37569
|
+
let readBufferLarge = null;
|
|
37570
|
+
while (true) {
|
|
37571
|
+
if (stream.destroyed) return;
|
|
37572
|
+
if (allJobsQueued && writeIndex >= jobs.length) break;
|
|
37573
|
+
if (!results.has(writeIndex)) {
|
|
37574
|
+
await new Promise((resolve) => {
|
|
37575
|
+
resumeWriter = resolve;
|
|
37576
|
+
});
|
|
37577
|
+
continue;
|
|
37578
|
+
}
|
|
37579
|
+
const result = results.get(writeIndex);
|
|
37580
|
+
results.delete(writeIndex);
|
|
37581
|
+
if (!result) {
|
|
37582
|
+
writeIndex++;
|
|
37583
|
+
controller();
|
|
37584
|
+
continue;
|
|
37585
|
+
}
|
|
37586
|
+
if (result.hardlinkId) {
|
|
37587
|
+
const originalName = result.header.name;
|
|
37588
|
+
const hardlinkTarget = seenHardlinks.get(result.hardlinkId);
|
|
37589
|
+
if (hardlinkTarget) {
|
|
37590
|
+
if (result.body && !(result.body instanceof Uint8Array) && !(result.body instanceof Readable)) await closeHandle(result.body.handle);
|
|
37591
|
+
result.body = void 0;
|
|
37592
|
+
result.header.type = LINK;
|
|
37593
|
+
result.header.linkname = hardlinkTarget.originalName;
|
|
37594
|
+
result.header.size = 0;
|
|
37595
|
+
}
|
|
37596
|
+
if (map) result.header = map(result.header);
|
|
37597
|
+
if (hardlinkTarget) {
|
|
37598
|
+
if (result.header.linkname === hardlinkTarget.originalName) result.header.linkname = hardlinkTarget.mappedName;
|
|
37599
|
+
} else seenHardlinks.set(result.hardlinkId, {
|
|
37600
|
+
originalName,
|
|
37601
|
+
mappedName: result.header.name
|
|
37602
|
+
});
|
|
37603
|
+
}
|
|
37604
|
+
packer.add(result.header);
|
|
37605
|
+
if (drain) await drain;
|
|
37606
|
+
if (stream.destroyed) return;
|
|
37607
|
+
if (result.body) if (result.body instanceof Uint8Array) {
|
|
37608
|
+
if (result.body.length > 0) {
|
|
37609
|
+
packer.write(result.body);
|
|
37610
|
+
if (drain) await drain;
|
|
37611
|
+
}
|
|
37612
|
+
} else if (result.body instanceof Readable) await writeStreamBody(result.body);
|
|
37613
|
+
else {
|
|
37614
|
+
const { handle, size } = result.body;
|
|
37615
|
+
const readBuffer = size > 1048576 ? readBufferLarge ??= Buffer.alloc(1048576) : readBufferSmall;
|
|
37616
|
+
try {
|
|
37617
|
+
let bytesLeft = size;
|
|
37618
|
+
while (bytesLeft > 0 && !stream.destroyed) {
|
|
37619
|
+
const { bytesRead } = await handle.read(readBuffer, 0, Math.min(bytesLeft, readBuffer.length), null);
|
|
37620
|
+
if (bytesRead === 0) break;
|
|
37621
|
+
packer.write(readBuffer.subarray(0, bytesRead));
|
|
37622
|
+
bytesLeft -= bytesRead;
|
|
37623
|
+
if (drain) await drain;
|
|
37624
|
+
}
|
|
37625
|
+
} finally {
|
|
37626
|
+
await closeHandle(handle);
|
|
37627
|
+
}
|
|
37628
|
+
}
|
|
37629
|
+
if (stream.destroyed) return;
|
|
37630
|
+
packer.endEntry();
|
|
37631
|
+
if (drain) await drain;
|
|
37632
|
+
writeIndex++;
|
|
37633
|
+
controller();
|
|
37634
|
+
}
|
|
37635
|
+
};
|
|
37636
|
+
const controller = () => {
|
|
37637
|
+
if (stream.destroyed || allJobsQueued) return;
|
|
37638
|
+
while (activeWorkers < concurrency && jobIndex < jobs.length && jobIndex - writeIndex < concurrency) {
|
|
37639
|
+
activeWorkers++;
|
|
37640
|
+
const currentIndex = jobIndex++;
|
|
37641
|
+
processJob(jobs[currentIndex], currentIndex).catch(onError).finally(() => {
|
|
37642
|
+
activeWorkers--;
|
|
37643
|
+
controller();
|
|
37644
|
+
});
|
|
37645
|
+
}
|
|
37646
|
+
if (activeWorkers === 0 && jobIndex >= jobs.length) {
|
|
37647
|
+
allJobsQueued = true;
|
|
37648
|
+
wakeWriter();
|
|
37649
|
+
}
|
|
37650
|
+
};
|
|
37651
|
+
const processJob = async (job, index) => {
|
|
37652
|
+
let jobResult = null;
|
|
37653
|
+
const target = normalizeName(job.target);
|
|
37654
|
+
try {
|
|
37655
|
+
if (job.type === "content" || job.type === "stream") {
|
|
37656
|
+
let body;
|
|
37657
|
+
let size;
|
|
37658
|
+
const isDir = target.endsWith("/");
|
|
37659
|
+
if (job.type === "stream") {
|
|
37660
|
+
if (!isDir && job.size <= 0 || isDir && job.size !== 0) throw new Error(isDir ? "Streams for directories must have size 0." : "Streams require a positive size.");
|
|
37661
|
+
size = job.size;
|
|
37662
|
+
} else {
|
|
37663
|
+
const content = await normalizeBody(job.content);
|
|
37664
|
+
size = content.length;
|
|
37665
|
+
body = content;
|
|
37666
|
+
}
|
|
37667
|
+
const stat = {
|
|
37668
|
+
size: isDir ? 0 : size,
|
|
37669
|
+
isFile: () => !isDir,
|
|
37670
|
+
isDirectory: () => isDir,
|
|
37671
|
+
isSymbolicLink: () => false,
|
|
37672
|
+
mode: job.mode,
|
|
37673
|
+
mtime: job.mtime ?? /* @__PURE__ */ new Date(),
|
|
37674
|
+
uid: job.uid ?? 0,
|
|
37675
|
+
gid: job.gid ?? 0
|
|
37676
|
+
};
|
|
37677
|
+
if (stream.destroyed) return;
|
|
37678
|
+
if (filter && !filter(target, stat)) return;
|
|
37679
|
+
if (stream.destroyed) return;
|
|
37680
|
+
let header = {
|
|
37681
|
+
name: target,
|
|
37682
|
+
type: isDir ? DIRECTORY : FILE,
|
|
37683
|
+
size: isDir ? 0 : size,
|
|
37684
|
+
mode: stat.mode,
|
|
37685
|
+
mtime: stat.mtime,
|
|
37686
|
+
uid: stat.uid,
|
|
37687
|
+
gid: stat.gid,
|
|
37688
|
+
uname: job.uname,
|
|
37689
|
+
gname: job.gname
|
|
37690
|
+
};
|
|
37691
|
+
if (map) header = map(header);
|
|
37692
|
+
if (stream.destroyed) return;
|
|
37693
|
+
if (!isDir && job.type === "stream") {
|
|
37694
|
+
body = job.content instanceof Readable ? job.content : Readable.fromWeb(job.content);
|
|
37695
|
+
body.once("error", onError);
|
|
37696
|
+
bodyStreams.add(body);
|
|
37697
|
+
}
|
|
37698
|
+
jobResult = {
|
|
37699
|
+
header,
|
|
37700
|
+
body: isDir ? void 0 : body
|
|
37701
|
+
};
|
|
37702
|
+
return;
|
|
37703
|
+
}
|
|
37704
|
+
let source = job.source;
|
|
37705
|
+
let stat = await fsp.lstat(source, BIGINT_STAT);
|
|
37706
|
+
if (stream.destroyed) return;
|
|
37707
|
+
if (dereference && (baseDir !== void 0 || stat.isSymbolicLink())) {
|
|
37708
|
+
const resolvedSource = await fsp.realpath(source);
|
|
37709
|
+
if (stream.destroyed) return;
|
|
37710
|
+
realBaseDir ??= await fsp.realpath(baseDir ?? directoryPath ?? process.cwd());
|
|
37711
|
+
if (stream.destroyed) return;
|
|
37712
|
+
const relativeToBase = path.relative(realBaseDir, resolvedSource);
|
|
37713
|
+
if (relativeToBase === ".." || relativeToBase.startsWith(".." + path.sep) || path.isAbsolute(relativeToBase)) return;
|
|
37714
|
+
if (stat.isSymbolicLink()) {
|
|
37715
|
+
source = resolvedSource;
|
|
37716
|
+
stat = await fsp.lstat(source, BIGINT_STAT);
|
|
37717
|
+
if (stat.isSymbolicLink()) return;
|
|
37718
|
+
}
|
|
37719
|
+
}
|
|
37720
|
+
if (stream.destroyed) return;
|
|
37721
|
+
if (filter && !filter(job.source, stat)) return;
|
|
37722
|
+
if (stream.destroyed) return;
|
|
37723
|
+
let header = {
|
|
37724
|
+
name: target,
|
|
37725
|
+
size: 0,
|
|
37726
|
+
mode: (job.mode ?? Number(stat.mode)) & 4095,
|
|
37727
|
+
mtime: job.mtime === void 0 ? stat.mtime : new Date(job.mtime.getTime()),
|
|
37728
|
+
uid: job.uid ?? Number(stat.uid),
|
|
37729
|
+
gid: job.gid ?? Number(stat.gid),
|
|
37730
|
+
uname: job.uname,
|
|
37731
|
+
gname: job.gname,
|
|
37732
|
+
type: FILE
|
|
37733
|
+
};
|
|
37734
|
+
let body;
|
|
37735
|
+
let hardlinkId;
|
|
37736
|
+
if (stat.isDirectory()) {
|
|
37737
|
+
header.type = DIRECTORY;
|
|
37738
|
+
header.name = target.endsWith("/") ? target : `${target}/`;
|
|
37739
|
+
try {
|
|
37740
|
+
const entries = await fsp.readdir(source, WITH_FILE_TYPES);
|
|
37741
|
+
if (stream.destroyed) return;
|
|
37742
|
+
const after = await fsp.lstat(source, BIGINT_STAT);
|
|
37743
|
+
if (stream.destroyed || !after.isDirectory() || stat.dev !== after.dev || stat.ino !== after.ino) return;
|
|
37744
|
+
for (const d of entries) jobs.push({
|
|
37745
|
+
type: d.isDirectory() ? DIRECTORY : FILE,
|
|
37746
|
+
source: path.join(source, d.name),
|
|
37747
|
+
target: `${header.name}${d.name}`,
|
|
37748
|
+
mtime: job.mtime,
|
|
37749
|
+
uid: job.uid,
|
|
37750
|
+
gid: job.gid,
|
|
37751
|
+
uname: job.uname,
|
|
37752
|
+
gname: job.gname,
|
|
37753
|
+
mode: job.mode
|
|
37754
|
+
});
|
|
37755
|
+
} catch (error) {
|
|
37756
|
+
const code = error.code;
|
|
37757
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
37758
|
+
}
|
|
37759
|
+
} else if (stat.isSymbolicLink()) {
|
|
37760
|
+
header.type = SYMLINK;
|
|
37761
|
+
header.linkname = await fsp.readlink(job.source);
|
|
37762
|
+
} else if (stat.isFile()) {
|
|
37763
|
+
header.size = Number(stat.size);
|
|
37764
|
+
let handleToClose;
|
|
37765
|
+
if (stat.nlink > 1n && (process.platform !== "win32" || stat.dev !== 0n && stat.ino !== -1n)) hardlinkId = `${stat.dev}:${stat.ino}`;
|
|
37766
|
+
try {
|
|
37767
|
+
let after;
|
|
37768
|
+
try {
|
|
37769
|
+
if (header.size === 0) after = await fsp.lstat(source, BIGINT_STAT);
|
|
37770
|
+
else {
|
|
37771
|
+
handleToClose = await fsp.open(source, fs.constants.O_NOFOLLOW ?? 0);
|
|
37772
|
+
fileHandles.set(handleToClose, null);
|
|
37773
|
+
}
|
|
37774
|
+
} catch (error) {
|
|
37775
|
+
const code = error.code;
|
|
37776
|
+
if (code === "ELOOP" || code === "ENOENT") return;
|
|
37777
|
+
throw error;
|
|
37778
|
+
}
|
|
37779
|
+
if (stream.destroyed) return;
|
|
37780
|
+
if (after) {
|
|
37781
|
+
if (!after.isFile() || stat.dev !== after.dev || stat.ino !== after.ino) return;
|
|
37782
|
+
} else {
|
|
37783
|
+
const { dev, ino } = await handleToClose.stat(BIGINT_STAT);
|
|
37784
|
+
if (stream.destroyed) return;
|
|
37785
|
+
if (stat.dev !== dev || stat.ino !== ino) return;
|
|
37786
|
+
}
|
|
37787
|
+
if (header.size > 0) {
|
|
37788
|
+
const handle = handleToClose;
|
|
37789
|
+
if (header.size < 32768) {
|
|
37790
|
+
const buffer = Buffer.allocUnsafe(header.size);
|
|
37791
|
+
let offset = 0;
|
|
37792
|
+
while (offset < buffer.length && !stream.destroyed) {
|
|
37793
|
+
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
|
|
37794
|
+
if (bytesRead === 0) break;
|
|
37795
|
+
offset += bytesRead;
|
|
37796
|
+
}
|
|
37797
|
+
body = offset === buffer.length ? buffer : buffer.subarray(0, offset);
|
|
37798
|
+
} else {
|
|
37799
|
+
body = {
|
|
37800
|
+
handle,
|
|
37801
|
+
size: header.size
|
|
37802
|
+
};
|
|
37803
|
+
handleToClose = void 0;
|
|
37804
|
+
}
|
|
37805
|
+
}
|
|
37806
|
+
} finally {
|
|
37807
|
+
if (handleToClose) await closeHandle(handleToClose);
|
|
37808
|
+
}
|
|
37809
|
+
} else return;
|
|
37810
|
+
if (stream.destroyed) return;
|
|
37811
|
+
if (hardlinkId) jobResult = {
|
|
37812
|
+
header,
|
|
37813
|
+
body,
|
|
37814
|
+
hardlinkId
|
|
37815
|
+
};
|
|
37816
|
+
else {
|
|
37817
|
+
if (map) header = map(header);
|
|
37818
|
+
jobResult = {
|
|
37819
|
+
header,
|
|
37820
|
+
body
|
|
37821
|
+
};
|
|
37822
|
+
}
|
|
37823
|
+
} finally {
|
|
37824
|
+
if (stream.destroyed) {
|
|
37825
|
+
if (jobResult?.body instanceof Readable) destroyBody(jobResult.body, cancelError);
|
|
37826
|
+
else if (jobResult?.body && !(jobResult.body instanceof Uint8Array)) await closeHandle(jobResult.body.handle);
|
|
37827
|
+
} else {
|
|
37828
|
+
results.set(index, jobResult);
|
|
37829
|
+
if (index === writeIndex) wakeWriter();
|
|
37830
|
+
}
|
|
37831
|
+
}
|
|
37832
|
+
};
|
|
37833
|
+
controller();
|
|
37834
|
+
await writer();
|
|
37835
|
+
if (!stream.destroyed) {
|
|
37836
|
+
packer.finalize();
|
|
37837
|
+
stream.push(null);
|
|
37838
|
+
}
|
|
37839
|
+
})().catch(onError);
|
|
37840
|
+
return stream;
|
|
37841
|
+
}
|
|
37842
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | (fs.constants.O_NOFOLLOW ?? 0) | fs.constants.O_EXCL;
|
|
37843
|
+
Promise.resolve();
|
|
37844
|
+
process.platform;
|
|
37589
37845
|
// src/helper/definitions.ts
|
|
37590
|
-
/** Every problem of every path, per
|
|
37591
|
-
var Rejected = TaggedStruct("Rejected", {
|
|
37592
|
-
skills: ArraySchema(Problem),
|
|
37593
|
-
agents: ArraySchema(Problem)
|
|
37594
|
-
});
|
|
37846
|
+
/** Every problem of every path, per kind; nothing is sent. */
|
|
37847
|
+
var Rejected = TaggedStruct("Rejected", { ...map$8(archiveKinds, () => ArraySchema(Problem)) });
|
|
37595
37848
|
var decoder = new TextDecoder();
|
|
37596
37849
|
var encoder = new TextEncoder();
|
|
37597
|
-
/** A definition file as the helper sends it: its text with the frontmatter repaired when strict YAML rejects it, and the
|
|
37598
|
-
var definitionFile = (
|
|
37850
|
+
/** A definition file as the helper sends it: its text with the frontmatter repaired when strict YAML rejects it, and the package source carrying that text. */
|
|
37851
|
+
var definitionFile = (target, bytes, mode) => {
|
|
37599
37852
|
const original = decoder.decode(bytes);
|
|
37600
37853
|
const text = repairFrontmatter(original);
|
|
37601
37854
|
return {
|
|
37602
37855
|
text,
|
|
37603
|
-
|
|
37604
|
-
|
|
37605
|
-
|
|
37856
|
+
source: {
|
|
37857
|
+
type: "content",
|
|
37858
|
+
target,
|
|
37859
|
+
content: text === original ? bytes : encoder.encode(text),
|
|
37606
37860
|
mode
|
|
37607
37861
|
}
|
|
37608
37862
|
};
|
|
@@ -37613,87 +37867,136 @@ var problemOf = (path, message) => ({
|
|
|
37613
37867
|
message
|
|
37614
37868
|
});
|
|
37615
37869
|
var isNotFound = (cause) => hasProperty(cause, "code") && cause.code === "ENOENT";
|
|
37616
|
-
var
|
|
37870
|
+
var statPath = (path) => tryPromise({
|
|
37617
37871
|
try: () => stat(path),
|
|
37618
37872
|
catch: (cause) => problemOf(path, isNotFound(cause) ? "does not exist" : `cannot be read: ${String(cause)}`)
|
|
37619
|
-
})
|
|
37620
|
-
var
|
|
37621
|
-
|
|
37622
|
-
|
|
37623
|
-
|
|
37624
|
-
withFileTypes: true
|
|
37625
|
-
});
|
|
37626
|
-
return (await Promise.all(dirents.map(async (dirent) => {
|
|
37627
|
-
const file = join(dirent.parentPath, dirent.name);
|
|
37628
|
-
const info = await stat(file);
|
|
37629
|
-
if (!info.isFile()) return;
|
|
37630
|
-
return {
|
|
37631
|
-
path: `${folder}/${relative(path, file).split(sep).join("/")}`,
|
|
37632
|
-
bytes: await readFile(file),
|
|
37633
|
-
mode: info.mode & 511
|
|
37634
|
-
};
|
|
37635
|
-
}))).filter((entry) => entry != null).toSorted((left, right) => left.path.localeCompare(right.path));
|
|
37636
|
-
},
|
|
37637
|
-
catch: (cause) => problemOf(path, `cannot be read: ${String(cause)}`)
|
|
37873
|
+
});
|
|
37874
|
+
var inspect = (path, kind) => statPath(path).pipe(filterOrFail((info) => kind === "folder" ? info.isDirectory() : info.isFile(), () => problemOf(path, `is not a ${kind}`)));
|
|
37875
|
+
var readText = (path, problemPath) => tryPromise({
|
|
37876
|
+
try: () => readFile(path),
|
|
37877
|
+
catch: (cause) => problemOf(problemPath, `cannot be read: ${String(cause)}`)
|
|
37638
37878
|
});
|
|
37639
37879
|
var readSkill = (path) => gen(function* () {
|
|
37640
37880
|
yield* inspect(path, "folder");
|
|
37641
|
-
const
|
|
37642
|
-
const
|
|
37643
|
-
const
|
|
37644
|
-
|
|
37645
|
-
const
|
|
37881
|
+
const root = resolve(path);
|
|
37882
|
+
const folder = basename(root);
|
|
37883
|
+
const skillPath = join(root, skillFile);
|
|
37884
|
+
const missing = problemOf(path, `has no ${skillFile}`);
|
|
37885
|
+
const info = yield* tryPromise({
|
|
37886
|
+
try: () => stat(skillPath),
|
|
37887
|
+
catch: (cause) => isNotFound(cause) ? missing : problemOf(path, `cannot be read: ${String(cause)}`)
|
|
37888
|
+
}).pipe(filterOrFail((found) => found.isFile(), () => missing));
|
|
37889
|
+
const { text, source } = definitionFile(`${folder}/${skillFile}`, yield* readText(skillPath, path), info.mode & 511);
|
|
37890
|
+
const realRoot = yield* tryPromise({
|
|
37891
|
+
try: () => realpath(root),
|
|
37892
|
+
catch: (cause) => problemOf(path, `cannot be read: ${String(cause)}`)
|
|
37893
|
+
});
|
|
37646
37894
|
return {
|
|
37647
|
-
|
|
37895
|
+
skill: {
|
|
37648
37896
|
folder,
|
|
37649
37897
|
path,
|
|
37650
37898
|
text
|
|
37651
37899
|
},
|
|
37652
|
-
|
|
37900
|
+
sources: [{
|
|
37901
|
+
type: "directory",
|
|
37902
|
+
source: realRoot,
|
|
37903
|
+
target: folder
|
|
37904
|
+
}, source],
|
|
37905
|
+
replaced: join(realRoot, skillFile)
|
|
37653
37906
|
};
|
|
37654
37907
|
});
|
|
37655
37908
|
var readAgent = (path) => gen(function* () {
|
|
37656
37909
|
const info = yield* inspect(path, "file");
|
|
37657
|
-
const
|
|
37658
|
-
try: () => readFile(path),
|
|
37659
|
-
catch: (cause) => problemOf(path, `cannot be read: ${String(cause)}`)
|
|
37660
|
-
});
|
|
37661
|
-
const { text, entry } = definitionFile(basename(path), bytes, info.mode & 511);
|
|
37910
|
+
const { text, source } = definitionFile(basename(path), yield* readText(path, path), info.mode & 511);
|
|
37662
37911
|
return {
|
|
37663
|
-
|
|
37912
|
+
agent: {
|
|
37664
37913
|
path,
|
|
37665
37914
|
text
|
|
37666
37915
|
},
|
|
37667
|
-
|
|
37916
|
+
sources: [source]
|
|
37668
37917
|
};
|
|
37669
37918
|
});
|
|
37670
|
-
|
|
37671
|
-
|
|
37672
|
-
|
|
37673
|
-
|
|
37674
|
-
|
|
37675
|
-
|
|
37919
|
+
/** A context path is checked by its stat alone: nothing in a folder is read here. */
|
|
37920
|
+
var readContext = (path) => gen(function* () {
|
|
37921
|
+
const info = yield* statPath(path);
|
|
37922
|
+
if (!info.isDirectory() && !info.isFile()) return yield* fail$3(problemOf(path, "is not a file or a folder"));
|
|
37923
|
+
const source = resolve(path);
|
|
37924
|
+
const target = basename(source);
|
|
37925
|
+
return info.isDirectory() ? {
|
|
37926
|
+
type: "directory",
|
|
37927
|
+
source,
|
|
37928
|
+
target
|
|
37929
|
+
} : {
|
|
37930
|
+
type: "file",
|
|
37931
|
+
source,
|
|
37932
|
+
target
|
|
37933
|
+
};
|
|
37934
|
+
});
|
|
37935
|
+
/** A problem on every context path whose name an earlier path already has. */
|
|
37936
|
+
var duplicateNames = (paths) => {
|
|
37937
|
+
const firstByName = /* @__PURE__ */ new Map();
|
|
37938
|
+
return paths.flatMap((path) => {
|
|
37939
|
+
const name = basename(resolve(path));
|
|
37940
|
+
const first = firstByName.get(name);
|
|
37941
|
+
if (first == null) {
|
|
37942
|
+
firstByName.set(name, path);
|
|
37943
|
+
return [];
|
|
37944
|
+
}
|
|
37945
|
+
return [problemOf(path, `has the same name as ${first}`)];
|
|
37946
|
+
});
|
|
37947
|
+
};
|
|
37948
|
+
/** Checks every path with the kind's reader (a stat for context paths, a read of the definition file for skills and agents), adds the problems the kind finds across the readable ones, and builds the result only when there is none. */
|
|
37949
|
+
var checkPaths = (paths, read, problemsOf, build) => gen(function* () {
|
|
37950
|
+
const [reads, unreadable] = yield* partition(paths, read, { concurrency: "unbounded" });
|
|
37951
|
+
const problems = [...unreadable, ...problemsOf(reads)];
|
|
37952
|
+
return problems.length > 0 ? yield* fail$3(problems) : build(reads);
|
|
37953
|
+
});
|
|
37954
|
+
var checkSkillPaths = (paths) => checkPaths(paths, readSkill, (reads) => checkSkills(reads.map((read) => read.skill)).problems, (reads) => {
|
|
37955
|
+
const replaced = new Set(reads.map((read) => read.replaced));
|
|
37956
|
+
return {
|
|
37957
|
+
sources: reads.flatMap((read) => read.sources),
|
|
37958
|
+
filter: (path) => !replaced.has(path)
|
|
37959
|
+
};
|
|
37960
|
+
});
|
|
37961
|
+
var checkAgentPaths = (paths) => checkPaths(paths, readAgent, (reads) => checkAgents(reads.map((read) => read.agent)).problems, (reads) => ({ sources: reads.flatMap((read) => read.sources) }));
|
|
37962
|
+
var checkContextPaths = (paths) => checkPaths(paths, readContext, () => duplicateNames(paths), (sources) => ({ sources }));
|
|
37963
|
+
var checks = {
|
|
37964
|
+
skills: checkSkillPaths,
|
|
37965
|
+
agents: checkAgentPaths,
|
|
37966
|
+
context: checkContextPaths
|
|
37967
|
+
};
|
|
37968
|
+
var pack = (kind, { sources, ...options }) => tryPromise({
|
|
37969
|
+
try: async () => {
|
|
37970
|
+
const chunks = [];
|
|
37971
|
+
await pipeline(packTar(sources, {
|
|
37972
|
+
dereference: true,
|
|
37973
|
+
baseDir: "/",
|
|
37974
|
+
...options
|
|
37975
|
+
}), createGzip(), async (packed) => {
|
|
37976
|
+
for await (const chunk of packed) chunks.push(chunk);
|
|
37977
|
+
});
|
|
37978
|
+
return Buffer.concat(chunks);
|
|
37979
|
+
},
|
|
37980
|
+
catch: (cause) => [problemOf(`the ${kind} archive`, `cannot be packed: ${String(cause)}`)]
|
|
37981
|
+
}).pipe(filterOrFail((archive) => archive.byteLength <= maxArchiveBytes, (archive) => [problemOf(`the ${kind} archive`, `is ${megabytes(archive.byteLength)}, over the ${megabytes(maxArchiveBytes)} limit`)]));
|
|
37982
|
+
var archiveOf = (kind, paths) => gen(function* () {
|
|
37983
|
+
if (paths.length === 0) return;
|
|
37984
|
+
return yield* checks[kind](paths).pipe(flatMap((checked) => pack(kind, checked)));
|
|
37676
37985
|
}).pipe(result$1);
|
|
37677
|
-
/**
|
|
37678
|
-
var
|
|
37679
|
-
const
|
|
37680
|
-
|
|
37681
|
-
if (isFailure(skills) || isFailure(agents)) return yield* fail$3(Rejected.make({
|
|
37682
|
-
skills: isFailure(skills) ? skills.failure : [],
|
|
37683
|
-
agents: isFailure(agents) ? agents.failure : []
|
|
37684
|
-
}));
|
|
37986
|
+
/** Checks every path the agent passed, kind by kind, and packs each kind that had paths into an archive; fails with every problem of every path, never at the first. */
|
|
37987
|
+
var packSpawnArchives = (paths) => gen(function* () {
|
|
37988
|
+
const results = yield* all$1(map$8(paths, (kindPaths, kind) => archiveOf(kind, kindPaths)), { concurrency: "unbounded" });
|
|
37989
|
+
if (Object.values(results).some(isFailure)) return yield* fail$3(Rejected.make(map$8(results, (result) => isFailure(result) ? result.failure : [])));
|
|
37685
37990
|
const archives = {};
|
|
37686
|
-
|
|
37687
|
-
|
|
37991
|
+
for (const kind of archiveKindNames) {
|
|
37992
|
+
const result = results[kind];
|
|
37993
|
+
if (isSuccess$1(result) && result.success != null) archives[kind] = result.success;
|
|
37994
|
+
}
|
|
37688
37995
|
return archives;
|
|
37689
37996
|
});
|
|
37690
37997
|
var section = (heading, problems) => `${heading}:\n${problems.map((problem) => `- ${problem.path}: ${problem.message}`).join("\n")}`;
|
|
37691
|
-
/** The tool's error text: every problem with its path,
|
|
37692
|
-
var rejectionText = (rejected) => [
|
|
37693
|
-
"No session was created: some skills or agents have problems.",
|
|
37694
|
-
...rejected.skills.length > 0 ? [section("Skills", rejected.skills)] : [],
|
|
37695
|
-
...rejected.agents.length > 0 ? [section("Agents", rejected.agents)] : []
|
|
37696
|
-
].join("\n\n");
|
|
37998
|
+
/** The tool's error text: every problem with its path, one section per kind with problems, in the kinds table's order. */
|
|
37999
|
+
var rejectionText = (rejected) => ["No session was created: some skills, agents or context have problems.", ...archiveKindNames.flatMap((kind) => rejected[kind].length > 0 ? [section(archiveKinds[kind].heading, rejected[kind])] : [])].join("\n\n");
|
|
37697
38000
|
// src/helper/command.ts
|
|
37698
38001
|
var sessionFields = { sessionId: SessionId };
|
|
37699
38002
|
var PromptRequest = Struct({
|
|
@@ -37721,9 +38024,10 @@ var commands = {
|
|
|
37721
38024
|
const request = yield* decodeArguments(SpawnRequest, json);
|
|
37722
38025
|
const defaults = yield* SettingsDefaults;
|
|
37723
38026
|
const client = yield* TentaclesClient;
|
|
37724
|
-
const archives = yield*
|
|
38027
|
+
const archives = yield* packSpawnArchives({
|
|
37725
38028
|
skills: request.skills ?? [],
|
|
37726
|
-
agents: request.agents ?? []
|
|
38029
|
+
agents: request.agents ?? [],
|
|
38030
|
+
context: request.context ?? []
|
|
37727
38031
|
});
|
|
37728
38032
|
return yield* client.sessions.create({ payload: {
|
|
37729
38033
|
name: request.name,
|