@chipschat/hermes 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +227 -24
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
|
-
`dist/main.js` in @chipschat/hermes 1.0.
|
|
3
|
+
`dist/main.js` in @chipschat/hermes 1.0.1 carries these packages, bundled in. Each one's
|
|
4
4
|
licence follows.
|
|
5
5
|
|
|
6
6
|
## @chipschat/harness 0.0.0 (Apache-2.0)
|
package/dist/main.js
CHANGED
|
@@ -20231,6 +20231,27 @@ var projectToken = external_exports.object({
|
|
|
20231
20231
|
expires_at: isoTimestamp,
|
|
20232
20232
|
path: external_exports.string().describe("The project's path on this server, which the device puts its own address in front of.")
|
|
20233
20233
|
});
|
|
20234
|
+
var projectFolderOpenAnswer = external_exports.object({
|
|
20235
|
+
macs: external_exports.int().nonnegative().describe("How many of the person's Macs were asked.")
|
|
20236
|
+
});
|
|
20237
|
+
var projectRun = external_exports.object({
|
|
20238
|
+
state: external_exports.enum(["never", "starting", "running", "ended", "failed", "stopped", "opened"]).describe("Never run on that Mac; starting (setup, or waiting for it to answer); running; ended by itself; failed; stopped by the person; or opened, for pages that need no starting."),
|
|
20239
|
+
command: external_exports.string().max(1100).optional().describe("What ran: `setup`, a newline, then `run`, or `open` and the file; the bar compares it with the recipe's."),
|
|
20240
|
+
since: isoTimestamp.optional(),
|
|
20241
|
+
ended: isoTimestamp.optional(),
|
|
20242
|
+
code: external_exports.int().optional().describe("How it ended, as its shell said."),
|
|
20243
|
+
opened: external_exports.string().max(600).optional().describe("What it opened, once it answered."),
|
|
20244
|
+
lines: external_exports.array(external_exports.string().max(400)).max(20).optional().describe("Its last lines, when it ended."),
|
|
20245
|
+
why: external_exports.string().max(400).optional().describe("Why the Mac didn't run it when it was last asked, in its words: no run file, or a different one.")
|
|
20246
|
+
});
|
|
20247
|
+
var heldRun = projectRun.extend({
|
|
20248
|
+
mac: external_exports.string().min(1).max(120).describe("The name of the Mac it runs on."),
|
|
20249
|
+
said_at: isoTimestamp
|
|
20250
|
+
});
|
|
20251
|
+
var projectRunAnswer = external_exports.object({
|
|
20252
|
+
run: heldRun.optional(),
|
|
20253
|
+
macs: external_exports.int().nonnegative()
|
|
20254
|
+
});
|
|
20234
20255
|
var commitId = external_exports.string().regex(/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/).describe("A commit, by its full git object id.");
|
|
20235
20256
|
var PROJECT_CHANGED_FILES = 200;
|
|
20236
20257
|
var projectChangedFile = external_exports.object({
|
|
@@ -20261,8 +20282,9 @@ function isSecretsFile(path) {
|
|
|
20261
20282
|
}
|
|
20262
20283
|
var SECRET_SHAPES = [
|
|
20263
20284
|
{ what: "a Chips token", source: "chips_(?:at|inv|j|pt)_[A-Za-z0-9_-]{20,}" },
|
|
20264
|
-
|
|
20265
|
-
{ what: "an
|
|
20285
|
+
// `sk-` begins a word, so a link's words ("…/elon-musk-terafab-project-…") are never a key.
|
|
20286
|
+
{ what: "an Anthropic key", source: "\\bsk-ant-[A-Za-z0-9_-]{20,}" },
|
|
20287
|
+
{ what: "an OpenAI key", source: "\\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{40,}" },
|
|
20266
20288
|
{ what: "an AWS access key", source: "\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b" },
|
|
20267
20289
|
{
|
|
20268
20290
|
what: "a GitHub token",
|
|
@@ -20332,6 +20354,7 @@ function asks(facts, options) {
|
|
|
20332
20354
|
"When your file and another's use different names for the same thing, the one that reached the project first keeps its names, and the other changes its names to match."
|
|
20333
20355
|
);
|
|
20334
20356
|
}
|
|
20357
|
+
lines2.push("When what you make is to be read (pages, PDFs, pictures), keep a start page at .chips/index.html that links to the documents worth seeing (its links start ../), with the documents themselves at the top or in folders of their own, and bring it up to date whenever you add, rename or replace one. Chips opens it in the app, and its links open there too. One document needs no start page.", `When what you make has to run on the person's Mac (a server, a script, an app), keep .chips/run.json so Run in Chips can start it from the project's folder: {"run": "the one shell command that starts it", "opens": "http://localhost:<port>, to open once it answers", "setup": "a command run first when it changes, like npm install"}. Start an app by its own program rather than with open, so Stop can close it. Leave out what it doesn't need, and check it works in your copy before you say it does.`);
|
|
20335
20358
|
if (facts.markdown)
|
|
20336
20359
|
lines2.push("In Markdown, put each sentence on a line of its own.");
|
|
20337
20360
|
if (facts.pages === true) {
|
|
@@ -21562,7 +21585,7 @@ var clientHelloFrame = external_exports.object({
|
|
|
21562
21585
|
autonomy: autonomyLevel.optional().describe("The level the harness believes it has.")
|
|
21563
21586
|
})).max(64).optional().describe("Not read by Chips: a connection is its token's, and the server's `hello` names the token's harness and its agents (ADR 0087). Absent for a person's client."),
|
|
21564
21587
|
since: external_exports.int().nonnegative().optional().describe("Not read by Chips: a stream resumes from `subscribe`'s `since`."),
|
|
21565
|
-
accepts: external_exports.array(external_exports.string().min(1).max(40)).max(32).optional().describe("Server frames beyond the base set this client understands, by the names in `SERVER_EXTENSIONS`. A name the server does not know is ignored rather than refused, so a client may name a frame a newer server sends and still be answered by an older one (ADR 0211, Phase 3). A client that says `events` is sent its replay in pages rather than one frame per event; one that says `presence` is sent an agent's state as it changes (ADR 0083); a harness's socket that says `restart` is sent `restart` when a person asks it to restart (ADR 0094); an owner's or admin's that says `join` is sent each join request as it changes (ADR 0122); a harness's that says `context` is sent `context.edit` when a person changes one of its context files (ADR 0134); a harness's that says `models` is sent `model.change` when a person changes an agent's model or effort (ADR 0140); a harness's that says `heartbeats` is sent `heartbeat.change` when a person switches an agent's heartbeat or changes how often it beats (ADR 0143); a harness's that says `context_windows` is sent `context_window.compact` and `context_window.change` when a person asks an agent to compact now or changes the limit it compacts itself at (ADR 0146); one that says nothing is sent what it was always sent, since an unknown frame is dropped in silence."),
|
|
21588
|
+
accepts: external_exports.array(external_exports.string().min(1).max(40)).max(32).optional().describe("Server frames beyond the base set this client understands, by the names in `SERVER_EXTENSIONS`. A name the server does not know is ignored rather than refused, so a client may name a frame a newer server sends and still be answered by an older one (ADR 0211, Phase 3). A client that says `events` is sent its replay in pages rather than one frame per event; one that says `presence` is sent an agent's state as it changes (ADR 0083); a harness's socket that says `restart` is sent `restart` when a person asks it to restart (ADR 0094); an owner's or admin's that says `join` is sent each join request as it changes (ADR 0122); a harness's that says `context` is sent `context.edit` when a person changes one of its context files (ADR 0134); a harness's that says `models` is sent `model.change` when a person changes an agent's model or effort (ADR 0140); a harness's that says `heartbeats` is sent `heartbeat.change` when a person switches an agent's heartbeat or changes how often it beats (ADR 0143); a harness's that says `context_windows` is sent `context_window.compact` and `context_window.change` when a person asks an agent to compact now or changes the limit it compacts itself at (ADR 0146); a person's that says `folders` is sent `folder.open` when they ask, from another of their devices, for a room's folder to be opened in Finder (ADR 0225), and `folder.run` when they ask for its project to be run or stopped there (ADR 0226); a person's that says `folder_runs` is sent `folder.run_state` when their Mac says how a run of theirs stands (ADR 0226); one that says nothing is sent what it was always sent, since an unknown frame is dropped in silence."),
|
|
21566
21589
|
context: contextBudget.optional().describe("How much of a room a harness is handed with each message for it (ADR 0088): 20 lines and 4,000 characters when absent, 200 and 32,000 at most. For this connection. A person's client is handed none.")
|
|
21567
21590
|
}).describe("The first frame a client sends.");
|
|
21568
21591
|
var subscribeFrame = external_exports.object({
|
|
@@ -21623,6 +21646,26 @@ var restartFrame = external_exports.object({
|
|
|
21623
21646
|
asked_by: id("person").describe("The person who asked."),
|
|
21624
21647
|
asked_at: isoTimestamp.describe("When they asked.")
|
|
21625
21648
|
}).describe("A person asks the harness to restart (ADR 0094). Sent to a harness's socket that said `restart` in its `hello`, now or never: it is not in the log, so a reconnect never hears it again. What runs is the harness's own restart.");
|
|
21649
|
+
var folderOpenFrame = external_exports.object({
|
|
21650
|
+
frame: external_exports.literal("folder.open"),
|
|
21651
|
+
space_id: id("space").describe("The room whose folder, or page, to open."),
|
|
21652
|
+
what: external_exports.enum(["folder", "page"]).optional().describe("`page`: open the room's start page, or its one document, in Chips for Mac (ADR 0227). Left out, the folder in Finder."),
|
|
21653
|
+
asked_from: external_exports.string().min(1).max(120).describe("The name of the device that asked: `iPhone`."),
|
|
21654
|
+
asked_at: isoTimestamp.describe("When they asked.")
|
|
21655
|
+
}).describe("A person asks their own Mac to open a room's folder in Finder (ADR 0225), or its page in Chips for Mac (ADR 0227), from another of their devices. Sent to that person's sockets that said `folders` in their `hello`, now or never: it is not in the log. The Mac finds the folder and the page itself: nothing here is a path.");
|
|
21656
|
+
var folderRunFrame = external_exports.object({
|
|
21657
|
+
frame: external_exports.literal("folder.run"),
|
|
21658
|
+
space_id: id("space").describe("The room whose project to run, or stop."),
|
|
21659
|
+
op: external_exports.enum(["run", "stop"]),
|
|
21660
|
+
command: external_exports.string().min(1).max(1100).optional().describe("For `run`, what the asking device showed would run: the Mac runs nothing else, and says so when its folder's run file says something different."),
|
|
21661
|
+
asked_from: external_exports.string().min(1).max(120).describe("The name of the device that asked: `iPhone`."),
|
|
21662
|
+
asked_at: isoTimestamp.describe("When they asked.")
|
|
21663
|
+
}).describe("A person asks their own Mac to run a room's project as its run file says, or to stop it (ADR 0226), from another of their devices. Sent to that person's sockets that said `folders` in their `hello`, now or never: it is not in the log. Nothing here is a command: the Mac reads it from the room's folder.");
|
|
21664
|
+
var folderRunStateFrame = external_exports.object({
|
|
21665
|
+
frame: external_exports.literal("folder.run_state"),
|
|
21666
|
+
space_id: id("space"),
|
|
21667
|
+
run: heldRun
|
|
21668
|
+
}).describe("How a person's run of a room's project on their Mac stands (ADR 0226), as that Mac said it. Sent to that person's sockets that said `folder_runs` in their `hello`, never to anyone else's, now or never: `GET /v1/spaces/:id/project/run` reads it again.");
|
|
21626
21669
|
var joinFrame = external_exports.object({
|
|
21627
21670
|
frame: external_exports.literal("join"),
|
|
21628
21671
|
request: joinRequestView
|
|
@@ -21665,6 +21708,9 @@ var serverFrame = external_exports.discriminatedUnion("frame", [
|
|
|
21665
21708
|
presenceFrame,
|
|
21666
21709
|
restartFrame,
|
|
21667
21710
|
joinFrame,
|
|
21711
|
+
folderOpenFrame,
|
|
21712
|
+
folderRunFrame,
|
|
21713
|
+
folderRunStateFrame,
|
|
21668
21714
|
contextEditFrame,
|
|
21669
21715
|
modelChangeFrame,
|
|
21670
21716
|
heartbeatChangeFrame,
|
|
@@ -21948,6 +21994,7 @@ import { existsSync } from "node:fs";
|
|
|
21948
21994
|
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
21949
21995
|
import { join } from "node:path";
|
|
21950
21996
|
var READ_UP_TO = 4 * 1024 * 1024;
|
|
21997
|
+
var MARKED_LINES = 14;
|
|
21951
21998
|
var TRIES = 4;
|
|
21952
21999
|
var MARKS = /^(<<<<<<<|>>>>>>>) /m;
|
|
21953
22000
|
var REJECTED = /\[rejected\]|fetch first|non-fast-forward/;
|
|
@@ -21986,6 +22033,27 @@ function pushedInWords(pushed) {
|
|
|
21986
22033
|
return `not sent: ${pushed.why}`;
|
|
21987
22034
|
}
|
|
21988
22035
|
}
|
|
22036
|
+
var stopped = (words4) => `${words4.trim().replace(/[.\s]+$/, "")}.`;
|
|
22037
|
+
function heldNote(pushed, name2) {
|
|
22038
|
+
switch (pushed.state) {
|
|
22039
|
+
case "sent":
|
|
22040
|
+
case "nothing":
|
|
22041
|
+
return void 0;
|
|
22042
|
+
case "clash": {
|
|
22043
|
+
const whose = pushed.with.length === 0 ? "another change" : `${namesInWords(pushed.with)}'s change`;
|
|
22044
|
+
return `(Not in the room's project yet: ${namesInWords(pushed.files)} clashed with ${whose}. ${name2} is asked to settle it next turn.)`;
|
|
22045
|
+
}
|
|
22046
|
+
case "keys": {
|
|
22047
|
+
const [first] = pushed.keys;
|
|
22048
|
+
const held = pushed.keys.length === 1 && first !== void 0 ? `${first.path} looks like it holds ${first.what.replace(/, by its name$/, "")}` : `${namesInWords(pushed.keys.map((key) => key.path))} look like they hold keys`;
|
|
22049
|
+
return `(Not in the room's project: ${held}, so nothing was sent.)`;
|
|
22050
|
+
}
|
|
22051
|
+
case "refused":
|
|
22052
|
+
return `(Not in the room's project: it was turned back: ${stopped(pushed.why)})`;
|
|
22053
|
+
case "failed":
|
|
22054
|
+
return `(Not in the room's project: it couldn't be sent: ${stopped(pushed.why)})`;
|
|
22055
|
+
}
|
|
22056
|
+
}
|
|
21989
22057
|
async function checkGit(git2 = "git", path = process.env["PATH"] ?? "/usr/bin:/bin") {
|
|
21990
22058
|
const [major, minor] = versionIn(PROJECT_GIT_VERSION) ?? [0, 0];
|
|
21991
22059
|
return new Promise((resolve5) => {
|
|
@@ -22005,7 +22073,7 @@ var Project = class {
|
|
|
22005
22073
|
remote;
|
|
22006
22074
|
constructor(options) {
|
|
22007
22075
|
this.options = options;
|
|
22008
|
-
this.copy = join(options.folder, "room");
|
|
22076
|
+
this.copy = options.person === true ? options.folder : join(options.folder, "room");
|
|
22009
22077
|
this.remote = `${options.url.replace(/\/$/, "").replace(/^ws/, "http")}${projectPath(options.spaceId)}`;
|
|
22010
22078
|
}
|
|
22011
22079
|
/** Before a turn: the copy made or brought up to date, and what the turn is to be told of it. */
|
|
@@ -22059,6 +22127,121 @@ var Project = class {
|
|
|
22059
22127
|
}
|
|
22060
22128
|
return { state: "failed", why: "others kept sending first." };
|
|
22061
22129
|
}
|
|
22130
|
+
/**
|
|
22131
|
+
* A person's folder made, or brought up to the room's `main` while nothing in it is theirs: no
|
|
22132
|
+
* file changed or added, nothing waiting to be sent, no clash (ADR 0224). Once something is
|
|
22133
|
+
* theirs, nothing in the folder changes; what others changed since is told instead.
|
|
22134
|
+
*/
|
|
22135
|
+
async look() {
|
|
22136
|
+
await this.ready();
|
|
22137
|
+
const fetched = await this.git(["fetch", "--quiet", "origin"]);
|
|
22138
|
+
const trouble = fetched.ok ? void 0 : remoteWords(fetched.err);
|
|
22139
|
+
const remote = await this.head("refs/remotes/origin/main");
|
|
22140
|
+
await this.settledAsTheirs(remote);
|
|
22141
|
+
const brought = await this.bringUntouched(remote);
|
|
22142
|
+
return { ...await this.standing(remote), ...brought, trouble };
|
|
22143
|
+
}
|
|
22144
|
+
/** The folder moved on to the room's `main` while nothing in it is theirs: what came in, whose. */
|
|
22145
|
+
async bringUntouched(remote) {
|
|
22146
|
+
const mine = await this.head("HEAD");
|
|
22147
|
+
const none = { came: [], cameBy: [] };
|
|
22148
|
+
if (remote === "" || mine === remote || !await this.untouched(mine, remote))
|
|
22149
|
+
return none;
|
|
22150
|
+
const moved = mine === "" ? await this.git(["checkout", "--quiet", "-B", PROJECT_BRANCH, remote]) : await this.git(["merge", "--quiet", "--ff-only", remote]);
|
|
22151
|
+
if (!moved.ok)
|
|
22152
|
+
return none;
|
|
22153
|
+
const from = mine === "" ? await this.emptyTree() : mine;
|
|
22154
|
+
return {
|
|
22155
|
+
came: lines((await this.git(["diff", "--name-only", from, remote])).out),
|
|
22156
|
+
cameBy: await this.authors(mine === "" ? remote : `${mine}..${remote}`)
|
|
22157
|
+
};
|
|
22158
|
+
}
|
|
22159
|
+
/**
|
|
22160
|
+
* A clash the person settled by taking the room's side whole: nothing of theirs is left to send,
|
|
22161
|
+
* so the folder goes back to the room's `main` and is kept up to date again. Every tracked file
|
|
22162
|
+
* is already the room's, and a file git doesn't track is never touched.
|
|
22163
|
+
*/
|
|
22164
|
+
async settledAsTheirs(remote) {
|
|
22165
|
+
if (remote === "" || !existsSync(join(this.copy, ".git", "MERGE_HEAD")))
|
|
22166
|
+
return;
|
|
22167
|
+
if ((await this.marked()).length > 0)
|
|
22168
|
+
return;
|
|
22169
|
+
if (!(await this.git(["diff", "--quiet", remote])).ok)
|
|
22170
|
+
return;
|
|
22171
|
+
await this.git(["reset", "--quiet", "--hard", remote]);
|
|
22172
|
+
}
|
|
22173
|
+
/** Whether nothing in the folder is the person's own: no change, no unsent commit, no clash. */
|
|
22174
|
+
async untouched(mine, remote) {
|
|
22175
|
+
if (existsSync(join(this.copy, ".git", "MERGE_HEAD")))
|
|
22176
|
+
return false;
|
|
22177
|
+
const status = await this.git(["status", "--porcelain", "--untracked-files=all"]);
|
|
22178
|
+
if (!status.ok || status.out.trim() !== "")
|
|
22179
|
+
return false;
|
|
22180
|
+
if (mine === "")
|
|
22181
|
+
return true;
|
|
22182
|
+
return (await this.git(["merge-base", "--is-ancestor", mine, remote])).ok;
|
|
22183
|
+
}
|
|
22184
|
+
/** What the person changed, a clash, and what others changed that the folder hasn't. */
|
|
22185
|
+
async standing(remote) {
|
|
22186
|
+
const mine = await this.head("HEAD");
|
|
22187
|
+
const base = await this.baseOf(mine, remote);
|
|
22188
|
+
const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((line) => {
|
|
22189
|
+
const [added2 = "0", removed = "0", ...path] = line.split(" ");
|
|
22190
|
+
return { path: path.join(" "), added: Number(added2) || 0, removed: Number(removed) || 0 };
|
|
22191
|
+
});
|
|
22192
|
+
const added = lines((await this.git(["ls-files", "--others", "--exclude-standard", "-z"])).out.replace(/\0/g, "\n"));
|
|
22193
|
+
const fresh = await Promise.all(added.map(async (path) => ({ path, added: await this.lineCount(path), removed: 0 })));
|
|
22194
|
+
const changed = [...counted, ...fresh].filter((change) => change.path !== "").sort((a, b) => a.path.localeCompare(b.path));
|
|
22195
|
+
const clashes = await this.marked();
|
|
22196
|
+
const clashedWith = await this.mergingWith();
|
|
22197
|
+
const first = clashes[0];
|
|
22198
|
+
const marked = first === void 0 ? void 0 : await this.markedLines(first);
|
|
22199
|
+
const since = remote === "" || remote === base ? [] : lines((await this.git(["diff", "--name-only", base, remote])).out);
|
|
22200
|
+
const by = since.length === 0 ? [] : await this.authors(mine === "" ? remote : `${base}..${remote}`);
|
|
22201
|
+
return { changed, clashes, clashedWith, marked, since, by };
|
|
22202
|
+
}
|
|
22203
|
+
/**
|
|
22204
|
+
* What the person's changes are measured from: where the folder and the room's `main` parted.
|
|
22205
|
+
* In a clash the room's side is already in the folder, so it is `main` itself: what differs from
|
|
22206
|
+
* it is what Send would change.
|
|
22207
|
+
*/
|
|
22208
|
+
async baseOf(mine, remote) {
|
|
22209
|
+
if (mine === "")
|
|
22210
|
+
return this.emptyTree();
|
|
22211
|
+
if (remote === "")
|
|
22212
|
+
return mine;
|
|
22213
|
+
if (existsSync(join(this.copy, ".git", "MERGE_HEAD")))
|
|
22214
|
+
return remote;
|
|
22215
|
+
return (await this.git(["merge-base", mine, remote])).out.trim() || mine;
|
|
22216
|
+
}
|
|
22217
|
+
/** How many lines a file the person added has: the lines it adds. None for a picture. */
|
|
22218
|
+
async lineCount(path) {
|
|
22219
|
+
try {
|
|
22220
|
+
const at = join(this.copy, path);
|
|
22221
|
+
if ((await stat(at)).size > READ_UP_TO)
|
|
22222
|
+
return 0;
|
|
22223
|
+
const words4 = await readFile(at, "utf8");
|
|
22224
|
+
if (words4.includes("\0"))
|
|
22225
|
+
return 0;
|
|
22226
|
+
return words4 === "" ? 0 : words4.split("\n").length - (words4.endsWith("\n") ? 1 : 0);
|
|
22227
|
+
} catch {
|
|
22228
|
+
return 0;
|
|
22229
|
+
}
|
|
22230
|
+
}
|
|
22231
|
+
/** The first clash in a file, from the line before its first mark to the line after its last. */
|
|
22232
|
+
async markedLines(path) {
|
|
22233
|
+
try {
|
|
22234
|
+
const all = (await readFile(join(this.copy, path), "utf8")).split("\n");
|
|
22235
|
+
const start = all.findIndex((line) => line.startsWith("<<<<<<< "));
|
|
22236
|
+
const end2 = all.findIndex((line, at) => at > start && line.startsWith(">>>>>>> "));
|
|
22237
|
+
if (start < 0 || end2 < 0)
|
|
22238
|
+
return void 0;
|
|
22239
|
+
const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
|
|
22240
|
+
return shown2.length <= MARKED_LINES ? shown2.join("\n") : [...shown2.slice(0, MARKED_LINES - 1), "\u2026"].join("\n");
|
|
22241
|
+
} catch {
|
|
22242
|
+
return void 0;
|
|
22243
|
+
}
|
|
22244
|
+
}
|
|
22062
22245
|
/**
|
|
22063
22246
|
* One try at sending: sent, nothing to send, or turned back; or, when somebody sent first, the
|
|
22064
22247
|
* agent's commits replayed on top of theirs, for the next try, unless they clashed.
|
|
@@ -22111,6 +22294,10 @@ ${sent.err}`;
|
|
|
22111
22294
|
const pointed = (await this.git(["remote", "get-url", "origin"])).out.trim();
|
|
22112
22295
|
if (pointed === this.remote)
|
|
22113
22296
|
return false;
|
|
22297
|
+
if (this.options.person === true) {
|
|
22298
|
+
await this.git(["remote", "set-url", "origin", this.remote]);
|
|
22299
|
+
return false;
|
|
22300
|
+
}
|
|
22114
22301
|
const was = /\/v1\/spaces\/([^/]+)\/project\.git$/.exec(pointed)?.[1] ?? "before";
|
|
22115
22302
|
await rename(this.copy, join(this.options.folder, `room-${was}`));
|
|
22116
22303
|
}
|
|
@@ -22264,9 +22451,12 @@ ${sent.err}`;
|
|
|
22264
22451
|
}
|
|
22265
22452
|
git(args) {
|
|
22266
22453
|
const { options } = this;
|
|
22454
|
+
const person = options.person === true;
|
|
22455
|
+
const email3 = `${options.agentId}@${person ? "people" : "agents"}.chips.invalid`;
|
|
22267
22456
|
const pairs = [
|
|
22268
22457
|
["http.extraHeader", `Authorization: Bearer ${options.token}`],
|
|
22269
|
-
|
|
22458
|
+
// A person's project token says who they are; an agent's harness token needs the agent.
|
|
22459
|
+
...person ? [] : [["http.extraHeader", `${PROJECT_ACTOR_HEADER}: ${options.agentId}`]],
|
|
22270
22460
|
["core.hooksPath", "/dev/null"],
|
|
22271
22461
|
["commit.gpgSign", "false"],
|
|
22272
22462
|
["merge.conflictStyle", "merge"],
|
|
@@ -22279,9 +22469,9 @@ ${sent.err}`;
|
|
|
22279
22469
|
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
22280
22470
|
GIT_TERMINAL_PROMPT: "0",
|
|
22281
22471
|
GIT_AUTHOR_NAME: options.name,
|
|
22282
|
-
GIT_AUTHOR_EMAIL:
|
|
22472
|
+
GIT_AUTHOR_EMAIL: email3,
|
|
22283
22473
|
GIT_COMMITTER_NAME: options.name,
|
|
22284
|
-
GIT_COMMITTER_EMAIL:
|
|
22474
|
+
GIT_COMMITTER_EMAIL: email3,
|
|
22285
22475
|
GIT_EDITOR: "true",
|
|
22286
22476
|
GIT_CONFIG_COUNT: String(pairs.length)
|
|
22287
22477
|
};
|
|
@@ -23990,7 +24180,11 @@ import { readFile as readFile3, realpath, stat as stat2 } from "node:fs/promises
|
|
|
23990
24180
|
import { homedir as homedir2 } from "node:os";
|
|
23991
24181
|
import { basename as basename2, isAbsolute, resolve, sep } from "node:path";
|
|
23992
24182
|
import { fileURLToPath } from "node:url";
|
|
23993
|
-
|
|
24183
|
+
function sendingFiles(options = {}) {
|
|
24184
|
+
const which = options.project === true ? "A file that isn't in the room's project" : "A file";
|
|
24185
|
+
return `${which} reaches this room only when you send it: put MEDIA:<path> on a line of its own in your answer, one line for each file, from your own folder. A path, or a link to this machine, reaches nobody: the people and agents here cannot open your folder.`;
|
|
24186
|
+
}
|
|
24187
|
+
var SENDING_FILES = sendingFiles();
|
|
23994
24188
|
var MOST_FILES = 20;
|
|
23995
24189
|
var MOST_FILE_BYTES = 100 * 1024 * 1024;
|
|
23996
24190
|
function filesNamedIn(answer) {
|
|
@@ -24098,7 +24292,7 @@ function promptFor(message, options = {}) {
|
|
|
24098
24292
|
const who = options.sayWho === false ? "" : `${withRole(message.from.display_name, message.from)}: `;
|
|
24099
24293
|
const files = (message.attachments ?? []).map(renderAttachment);
|
|
24100
24294
|
const words4 = [`${who}${message.text}`, ...files].join("\n");
|
|
24101
|
-
const sending = options.sendsFiles === true ?
|
|
24295
|
+
const sending = options.sendsFiles === true ? sendingFiles({ project: options.project !== void 0 }) : void 0;
|
|
24102
24296
|
const replying = renderReplyingTo(message.replying_to, {
|
|
24103
24297
|
sender: { id: message.from.participant_id, name: message.from.display_name },
|
|
24104
24298
|
taker,
|
|
@@ -24576,9 +24770,13 @@ async function failureOf(answer) {
|
|
|
24576
24770
|
|
|
24577
24771
|
// src/bridge.ts
|
|
24578
24772
|
var WANTS = ["agents", "new_agents", "forks"];
|
|
24579
|
-
|
|
24580
|
-
|
|
24581
|
-
|
|
24773
|
+
function filesHere(project = false) {
|
|
24774
|
+
return `${sendingFiles({ project })} Hermes's note that this API server sends only pictures, and shows a MEDIA: tag on the runs endpoint as text, does not hold for these runs: they come from Chips, whose bridge takes each MEDIA: line out of your answer and attaches that file to your message, whatever its type, up to 100 MB.`;
|
|
24775
|
+
}
|
|
24776
|
+
var FILES_HERE = filesHere();
|
|
24777
|
+
function instructionsFor(folder2, project = false) {
|
|
24778
|
+
const files = filesHere(project);
|
|
24779
|
+
return folder2 === void 0 ? files : `${files} Your own folder is ${folder2}: make and keep your files there, and run your commands from there.`;
|
|
24582
24780
|
}
|
|
24583
24781
|
function freeFolder(root, name2, taken) {
|
|
24584
24782
|
const slug = name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
@@ -24800,18 +24998,22 @@ var Bridge = class {
|
|
|
24800
24998
|
}
|
|
24801
24999
|
/**
|
|
24802
25000
|
* What the turn changed in its copy, sent to the room's project before its words are posted: the
|
|
24803
|
-
* commit it left `main` at, for the reply to carry (Chips ADR 0222), when it sent one
|
|
25001
|
+
* commit it left `main` at, for the reply to carry (Chips ADR 0222), when it sent one; else, when
|
|
25002
|
+
* the change didn't go, the line the reply says so in (Chips AP-6).
|
|
24804
25003
|
*/
|
|
24805
25004
|
async sendProject(turn, words4) {
|
|
24806
|
-
if (turn.project === void 0) return
|
|
25005
|
+
if (turn.project === void 0) return {};
|
|
24807
25006
|
const { copy, name: name2 } = turn.project;
|
|
24808
25007
|
try {
|
|
24809
25008
|
const pushed = await copy.push({ words: words4 });
|
|
24810
25009
|
this.log(`${name2}'s project: ${pushedInWords(pushed)}`);
|
|
24811
|
-
|
|
25010
|
+
if (pushed.state === "sent") return { change: pushed.commit };
|
|
25011
|
+
const note = heldNote(pushed, name2);
|
|
25012
|
+
return note === void 0 ? {} : { note };
|
|
24812
25013
|
} catch (error61) {
|
|
24813
25014
|
this.log(`${name2}'s project couldn't be sent: ${messageOf(error61)}`);
|
|
24814
|
-
|
|
25015
|
+
const note = heldNote({ state: "failed", why: messageOf(error61) }, name2);
|
|
25016
|
+
return note === void 0 ? {} : { note };
|
|
24815
25017
|
}
|
|
24816
25018
|
}
|
|
24817
25019
|
/** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083). */
|
|
@@ -24979,7 +25181,7 @@ var Bridge = class {
|
|
|
24979
25181
|
async once(turn, session, text, key) {
|
|
24980
25182
|
const { agent } = turn;
|
|
24981
25183
|
try {
|
|
24982
|
-
turn.hermesRun = await agent.client.startRun({ text, sessionId: session, model: agent.config.model, key, instructions: instructionsFor(turn.folder) });
|
|
25184
|
+
turn.hermesRun = await agent.client.startRun({ text, sessionId: session, model: agent.config.model, key, instructions: instructionsFor(turn.folder, turn.project !== void 0) });
|
|
24983
25185
|
} catch (error61) {
|
|
24984
25186
|
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${messageOf(error61)}`, true);
|
|
24985
25187
|
return void 0;
|
|
@@ -25142,8 +25344,8 @@ var Bridge = class {
|
|
|
25142
25344
|
${command}
|
|
25143
25345
|
\`\`\`` } }
|
|
25144
25346
|
});
|
|
25145
|
-
const
|
|
25146
|
-
const decision = await Promise.race([asked,
|
|
25347
|
+
const stopped2 = new Promise((resolve5) => turn.run.on("cancel", () => resolve5(void 0)));
|
|
25348
|
+
const decision = await Promise.race([asked, stopped2]);
|
|
25147
25349
|
const pending = turn.approvals.get(requestId ?? approvalId);
|
|
25148
25350
|
if (decision === void 0 || pending === void 0 || pending.answered) return;
|
|
25149
25351
|
pending.answered = true;
|
|
@@ -25186,10 +25388,11 @@ ${command}
|
|
|
25186
25388
|
await this.counted(turn, last);
|
|
25187
25389
|
const output2 = typeof last["output"] === "string" ? last["output"].trim() : "";
|
|
25188
25390
|
const answer = output2 !== "" ? output2 : turn.reply.trim();
|
|
25189
|
-
const change = await this.sendProject(turn, answer);
|
|
25391
|
+
const { change, note } = await this.sendProject(turn, answer);
|
|
25190
25392
|
const { folder: folder2 } = turn;
|
|
25191
25393
|
const { chips: chips2 } = this.options;
|
|
25192
|
-
const
|
|
25394
|
+
const words4 = [answer, note ?? ""].filter((part) => part !== "").join("\n\n");
|
|
25395
|
+
const post = await sendAnswer(words4, {
|
|
25193
25396
|
folders: folder2 === void 0 ? [] : [folder2],
|
|
25194
25397
|
upload: (input2) => chips2.upload(input2)
|
|
25195
25398
|
});
|
|
@@ -25331,9 +25534,9 @@ var end = (line, code) => {
|
|
|
25331
25534
|
};
|
|
25332
25535
|
function stopIfAsked(label2) {
|
|
25333
25536
|
if (!flags.includes("--stop")) return;
|
|
25334
|
-
const
|
|
25335
|
-
if (
|
|
25336
|
-
if (
|
|
25537
|
+
const stopped2 = stopKeeping(label2);
|
|
25538
|
+
if (stopped2.state === "stopped") end(`Stopped ${label2}, and removed ${stopped2.path}.`, 0);
|
|
25539
|
+
if (stopped2.state === "theirs") end(`${stopped2.path} wasn't written by this bridge: it was left as it is.`, 1);
|
|
25337
25540
|
end(`Nothing called ${label2} was kept running here.`, 0);
|
|
25338
25541
|
}
|
|
25339
25542
|
function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
package/package.json
CHANGED