@chipschat/hermes 1.0.8 → 1.0.11
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/README.md +5 -5
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +180 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ on Hermes's own API server.
|
|
|
14
14
|
its original's profile, in its original's folder: Hermes forks a session only within its
|
|
15
15
|
profile, so a copy has no profile of its own.
|
|
16
16
|
|
|
17
|
-
Apache-2.0
|
|
17
|
+
Apache-2.0, published on npm as `@chipschat/hermes`.
|
|
18
18
|
|
|
19
19
|
## What Chips shows
|
|
20
20
|
|
|
@@ -83,11 +83,11 @@ hasn't been tried through Chips yet.
|
|
|
83
83
|
|
|
84
84
|
## From Chips's gallery
|
|
85
85
|
|
|
86
|
-
The card's line is `chips connect hermes
|
|
86
|
+
The card's line is `CHIPS_JOIN_LINK='<link>' chips connect hermes` (Chips ADR 0218). It downloads this bridge as the npm package
|
|
87
87
|
`@chipschat/hermes`, the newest 1.x, checks it against the registry's checksum, and runs it on the Node
|
|
88
88
|
that Chips carries, so the machine needs no Node or npm of its own. The bridge checks Hermes,
|
|
89
89
|
joins, and keeps itself running from login. `chips disconnect hermes` stops it. On another computer,
|
|
90
|
-
`curl -fsSL https://chipschat.ai/install | sh -s -- connect hermes
|
|
90
|
+
`curl -fsSL https://chipschat.ai/install | CHIPS_JOIN_LINK='<link>' sh -s -- connect hermes` installs Chips there
|
|
91
91
|
first. The steps below run the bridge from this checkout instead, to work on it.
|
|
92
92
|
|
|
93
93
|
## Run it
|
|
@@ -103,11 +103,11 @@ first. The steps below run the bridge from this checkout instead, to work on it.
|
|
|
103
103
|
|
|
104
104
|
2. Once, in this repository: `npm install`.
|
|
105
105
|
|
|
106
|
-
3. Join Chips once, with a link from Chips's gallery (
|
|
106
|
+
3. Join Chips once, with a link from Chips's gallery (Agents → Connect a harness → Hermes), whose card gives
|
|
107
107
|
this line with the link in it:
|
|
108
108
|
|
|
109
109
|
```
|
|
110
|
-
cd
|
|
110
|
+
cd chips-hermes && CHIPS_JOIN_LINK='<link>' npm run keep
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
`npm run keep` joins, then keeps the bridge running from login (below); `npm start` is the
|
|
@@ -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.11 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
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
|
|
10
10
|
// src/main.ts
|
|
11
11
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
12
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as
|
|
12
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13
13
|
import { createServer as createServer2 } from "node:net";
|
|
14
14
|
import { homedir as homedir8 } from "node:os";
|
|
15
15
|
import { dirname as dirname6, join as join13, resolve as resolve6 } from "node:path";
|
|
@@ -19945,8 +19945,9 @@ var policyRule = external_exports.object({
|
|
|
19945
19945
|
external: external_exports.boolean().optional(),
|
|
19946
19946
|
irreversible: external_exports.boolean().optional(),
|
|
19947
19947
|
money_usd_over: external_exports.number().nonnegative().optional(),
|
|
19948
|
-
risk: external_exports.array(riskClass).max(8).optional()
|
|
19949
|
-
|
|
19948
|
+
risk: external_exports.array(riskClass).max(8).optional(),
|
|
19949
|
+
starts_with: external_exports.array(external_exports.string().min(1).max(400)).min(1).max(8).optional().describe("For an action that carries a command (ADR 0239): the words a part of it starts with. A command goes ahead on such rules only when every part starts with one that allows it.")
|
|
19950
|
+
}).optional().describe("Extra conditions on the action's flags, and for a command, how its parts start."),
|
|
19950
19951
|
decision: external_exports.enum(["allow", "ask", "deny"]).describe("What to do when the rule matches."),
|
|
19951
19952
|
approvers: external_exports.array(approverRef).max(16).optional().describe("Who may answer, when the decision is `ask`."),
|
|
19952
19953
|
reason: external_exports.string().max(400).optional().describe("Shown to the harness, and recorded in the audit log."),
|
|
@@ -20465,9 +20466,17 @@ var roomIdle = external_exports.object({
|
|
|
20465
20466
|
var roomNote = external_exports.object({
|
|
20466
20467
|
members: external_exports.array(roomMember).max(500).describe("The room's people, then its agents, each by name, anybody removed left out."),
|
|
20467
20468
|
notes: external_exports.record(external_exports.string().max(120), external_exports.string().max(64e3)).describe("The note for each who takes the turn (`for`), by their id: who is here, with handles, and that only those @-mentioned act. A harness puts it at the top of the turn."),
|
|
20469
|
+
rules: external_exports.record(external_exports.string().max(120), external_exports.string().max(4e3)).optional().describe("How the space works, for each who takes the turn, by their id: the words its note ends with (ADR 0250), that everyone here reads what it writes and, beside other agents, what an @ does and when to answer NO_REPLY. They stay the same while the space's agents do, so a harness whose session keeps its earlier turns may say them once, and the note without them after."),
|
|
20468
20470
|
working: external_exports.array(roomWork).max(500).optional().describe("The room's agents with a turn going as Chips sends this one (ADR 0104), the takers' own included. A turn nothing has been heard from for ten minutes is not counted."),
|
|
20469
20471
|
idle: external_exports.array(roomIdle).max(500).optional().describe("The room's other agents that have had a turn here: how long since the last one ended (ADR 0104).")
|
|
20470
20472
|
});
|
|
20473
|
+
function noteWithoutRules(room, taker) {
|
|
20474
|
+
const note = room?.notes[taker];
|
|
20475
|
+
const rules = room?.rules?.[taker];
|
|
20476
|
+
if (note === void 0 || rules === void 0 || !note.endsWith(rules))
|
|
20477
|
+
return note;
|
|
20478
|
+
return note.slice(0, note.length - rules.length).trimEnd();
|
|
20479
|
+
}
|
|
20471
20480
|
var MONTHS = [
|
|
20472
20481
|
"January",
|
|
20473
20482
|
"February",
|
|
@@ -20487,8 +20496,8 @@ var MONTHS = [
|
|
|
20487
20496
|
var CONTEXT_CEILING = { lines: 200, chars: 32e3 };
|
|
20488
20497
|
var CONTEXT_HEADING = "Said in this space since you last spoke:";
|
|
20489
20498
|
var contextBudget = external_exports.object({
|
|
20490
|
-
lines: external_exports.int().nonnegative().optional().describe("The most lines to be handed,
|
|
20491
|
-
chars: external_exports.int().nonnegative().optional().describe("The most characters those lines may run to, as `Name: words` one per line,
|
|
20499
|
+
lines: external_exports.int().nonnegative().optional().describe("The most lines to be handed, 40 when absent. Chips hands over 200 at most, and none when this is 0."),
|
|
20500
|
+
chars: external_exports.int().nonnegative().optional().describe("The most characters those lines may run to, as `Name: words` one per line, 16,000 when absent. Chips hands over 32,000 at most.")
|
|
20492
20501
|
}).describe("How much of the room a harness is handed with a message for it (ADR 0088). Its own to say: a budget promises nothing to anybody else.");
|
|
20493
20502
|
var contextLine = external_exports.object({
|
|
20494
20503
|
seq: external_exports.int().positive().describe("Where in the log it happened."),
|
|
@@ -20868,7 +20877,7 @@ var messageNew = defineEvent({
|
|
|
20868
20877
|
summary: "Someone said something, with any attachments as signed URLs the harness can fetch.",
|
|
20869
20878
|
requiresRun: false,
|
|
20870
20879
|
payload: deliveredMessage.extend({
|
|
20871
|
-
context: roomContext.optional().describe("What was said in the room since this harness last spoke there, or since it joined when it has not, up to this message and without it (ADR 0088). From when the turn that said its last words began, so what was said while it worked is here, and its own words are not (ADR 0105). Worked out from the log, so a replay hands over the same lines. As many as the budget the harness gave in its `hello`:
|
|
20880
|
+
context: roomContext.optional().describe("What was said in the room since this harness last spoke there, or since it joined when it has not, up to this message and without it (ADR 0088). From when the turn that said its last words began, so what was said while it worked is here, and its own words are not (ADR 0105). Worked out from the log, so a replay hands over the same lines. As many as the budget the harness gave in its `hello`: 40 lines and 16,000 characters when it gave none, and none when it asked for 0. When older lines were left out, the taker's room note says so."),
|
|
20872
20881
|
room: roomNote.optional().describe("Who is in the room, with the handle that reaches each, and a note for each who takes the turn saying so and what an @ does (ADR 0092). A harness puts `notes[<taker>]` at the top of that turn. Absent on a message in no room."),
|
|
20873
20882
|
exchange: deliveredExchange.optional().describe("How the agents' talk in the room stands, once it is worth a word (ADR 0097): from eight messages between agents since a person last acted or an agent last made something new. A harness puts `note` at the top of the turn, as Chips's, ahead of the room's note. Absent below that, and worked out from the log, so a replay hands over the same facts."),
|
|
20874
20883
|
handed: external_exports.literal(true).optional().describe("Chips held this message when it was said and hands it over now a person has acted (ADR 0097): the newest of an agent's held messages, once; or, with `lead`, hands it to the room's lead. Its seq and id are the hand-over's, its `message_id` the message's own."),
|
|
@@ -20918,6 +20927,7 @@ var approvalResolved = defineEvent({
|
|
|
20918
20927
|
approval_id: id("approval"),
|
|
20919
20928
|
decision: external_exports.enum(["approved", "edited", "denied", "expired"]).describe("`expired` means nobody answered and the fallback applied."),
|
|
20920
20929
|
scope: external_exports.enum(["once", "session", "always"]).default("once").describe("How far the answer reaches. `always` becomes a policy rule."),
|
|
20930
|
+
everywhere: external_exports.boolean().optional().describe("With `always`: the rule is the harness's in every space, not only this one (ADR 0239). An owner or an admin only."),
|
|
20921
20931
|
updated_input: external_exports.unknown().optional().describe("The edited action input, when the decision is `edited`."),
|
|
20922
20932
|
reason: external_exports.string().max(4e3).optional().describe("Why. The harness gets to read this."),
|
|
20923
20933
|
resolved_by: external_exports.string().min(1).max(120).describe("The participant who answered, or `system` on expiry \u2014 a harness when it settles its own ask, answered in its own tools. Filled in by Chips from the credential that sent it; a value the sender writes is replaced."),
|
|
@@ -21182,6 +21192,10 @@ function namesInWords(names) {
|
|
|
21182
21192
|
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
21183
21193
|
}
|
|
21184
21194
|
function renderProjectNote(facts, options) {
|
|
21195
|
+
return `${renderProjectFacts(facts)}
|
|
21196
|
+
${renderProjectAsks(facts, options)}`;
|
|
21197
|
+
}
|
|
21198
|
+
function renderProjectFacts(facts) {
|
|
21185
21199
|
const files = `${facts.files} file${facts.files === 1 ? "" : "s"}`;
|
|
21186
21200
|
const lines2 = [
|
|
21187
21201
|
`The space's project is a git repository. Your copy is ${facts.folder} (${files}), ${facts.trouble === void 0 ? "brought up to date just now" : "not brought up to date"}.`
|
|
@@ -21199,9 +21213,9 @@ function renderProjectNote(facts, options) {
|
|
|
21199
21213
|
for (const key of facts.keys) {
|
|
21200
21214
|
lines2.push(`${key.path} holds what looks like ${key.what}, so your last change wasn't sent: take it out and read it from outside the project.`);
|
|
21201
21215
|
}
|
|
21202
|
-
return
|
|
21216
|
+
return lines2.join("\n");
|
|
21203
21217
|
}
|
|
21204
|
-
function
|
|
21218
|
+
function renderProjectAsks(facts, options) {
|
|
21205
21219
|
const lines2 = [
|
|
21206
21220
|
"Work on the project in that folder and leave git to Chips: when your turn ends, what you changed is committed under your name and sent to everyone here. Keep scratch work outside it.",
|
|
21207
21221
|
// Sent with the answer too, a project's file became a room file of its own, with versions of
|
|
@@ -21222,7 +21236,7 @@ function asks(facts, options) {
|
|
|
21222
21236
|
if (facts.pages === true) {
|
|
21223
21237
|
lines2.push("A page opens in Chips as one document: name every file it needs in its tags and imports (<link>, <script src>, <img src>, import), since it can't fetch files while it runs.");
|
|
21224
21238
|
}
|
|
21225
|
-
return lines2;
|
|
21239
|
+
return lines2.join("\n");
|
|
21226
21240
|
}
|
|
21227
21241
|
|
|
21228
21242
|
// node_modules/@chipschat/protocol/dist/src/events/harness-to-chips.js
|
|
@@ -21874,7 +21888,7 @@ var membershipRemoved = defineEvent({
|
|
|
21874
21888
|
payload: external_exports.object({
|
|
21875
21889
|
participant_id: external_exports.string().min(1).max(120).describe("Who left."),
|
|
21876
21890
|
removed_by: external_exports.string().min(1).max(120).optional().describe("Who removed them, if not themselves."),
|
|
21877
|
-
reason: external_exports.enum([...RELEASE_REASONS, "person_removed"]).optional().describe("For an agent (ADR 0087): why it left, as `agent.released` says. For a person, `person_removed`: they were removed from the workspace (ADR 0160). Written by Chips; absent, a person took them out or they left."),
|
|
21891
|
+
reason: external_exports.enum([...RELEASE_REASONS, "person_removed", "person_left"]).optional().describe("For an agent (ADR 0087): why it left, as `agent.released` says. For a person, `person_removed`: they were removed from the workspace (ADR 0160); `person_left`: they deleted their account and left it (ADR 0243). Written by Chips; absent, a person took them out or they left the room."),
|
|
21878
21892
|
moved_to: id("space").optional().describe("For an agent moved away: the room it went to, so this room can say so.")
|
|
21879
21893
|
})
|
|
21880
21894
|
});
|
|
@@ -22183,14 +22197,14 @@ var personRemoved = defineEvent({
|
|
|
22183
22197
|
type: "person.removed",
|
|
22184
22198
|
direction: "workspace",
|
|
22185
22199
|
sender: "chips",
|
|
22186
|
-
summary: "A person was removed from the workspace: signed out everywhere, out of every room, and the harnesses they owned handed
|
|
22200
|
+
summary: "A person was removed from the workspace, or deleted their account: signed out everywhere, out of every room, and the harnesses they owned handed on. What they said stays, under their name.",
|
|
22187
22201
|
requiresRun: false,
|
|
22188
22202
|
payload: external_exports.object({
|
|
22189
22203
|
participant_id: id("person").describe("Who was removed."),
|
|
22190
22204
|
display_name: external_exports.string().min(1).max(120).describe("What they were called when they were removed, so the audit needs no lookup."),
|
|
22191
|
-
removed_by: id("person").describe("The owner or admin who removed them."),
|
|
22205
|
+
removed_by: id("person").describe("The owner or admin who removed them, or they themselves when they deleted their account (ADR 0243)."),
|
|
22192
22206
|
spaces: external_exports.array(id("space")).max(1e3).optional().describe("The rooms they were taken out of, each with its own `membership.removed`."),
|
|
22193
|
-
harnesses: external_exports.array(id("harness")).max(1e3).optional().describe("The harnesses they owned, handed to `removed_by
|
|
22207
|
+
harnesses: external_exports.array(id("harness")).max(1e3).optional().describe("The harnesses they owned, each handed on by `harness.transferred`: to `removed_by`, or, for an account deleted, to an owner.")
|
|
22194
22208
|
})
|
|
22195
22209
|
});
|
|
22196
22210
|
var joinWho = {
|
|
@@ -22536,7 +22550,7 @@ var clientHelloFrame = external_exports.object({
|
|
|
22536
22550
|
})).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."),
|
|
22537
22551
|
since: external_exports.int().nonnegative().optional().describe("Not read by Chips: a stream resumes from `subscribe`'s `since`."),
|
|
22538
22552
|
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."),
|
|
22539
|
-
context: contextBudget.optional().describe("How much of a room a harness is handed with each message for it (ADR 0088):
|
|
22553
|
+
context: contextBudget.optional().describe("How much of a room a harness is handed with each message for it (ADR 0088): 40 lines and 16,000 characters when absent, 200 and 32,000 at most. For this connection. A person's client is handed none.")
|
|
22540
22554
|
}).describe("The first frame a client sends.");
|
|
22541
22555
|
var subscribeFrame = external_exports.object({
|
|
22542
22556
|
frame: external_exports.literal("subscribe"),
|
|
@@ -23793,6 +23807,14 @@ var Run = class {
|
|
|
23793
23807
|
...cost2(input2.costUsd)
|
|
23794
23808
|
});
|
|
23795
23809
|
}
|
|
23810
|
+
/**
|
|
23811
|
+
* That this turn heard a message said in the room while it worked (ADR 0106): one that
|
|
23812
|
+
* `message.overheard` named this run for in `working_turns`, handed to the turn at a step. The
|
|
23813
|
+
* room shows its agent heard it while working. Say it once the turn has the words, never before.
|
|
23814
|
+
*/
|
|
23815
|
+
heard(messageId) {
|
|
23816
|
+
return this.wire.send("message.heard", { message_id: messageId });
|
|
23817
|
+
}
|
|
23796
23818
|
checkpoint(label2, next, compacted) {
|
|
23797
23819
|
return this.wire.send("checkpoint", {
|
|
23798
23820
|
label: label2,
|
|
@@ -24824,6 +24846,25 @@ function connect(options) {
|
|
|
24824
24846
|
return Chips.connect(options);
|
|
24825
24847
|
}
|
|
24826
24848
|
|
|
24849
|
+
// node_modules/@chipschat/harness/dist/heard.js
|
|
24850
|
+
function workingTurnsOf(message) {
|
|
24851
|
+
return (message.working_turns ?? []).map((turn) => ({
|
|
24852
|
+
agentId: turn.agent_id,
|
|
24853
|
+
runId: turn.run_id
|
|
24854
|
+
}));
|
|
24855
|
+
}
|
|
24856
|
+
function overheardLine(message) {
|
|
24857
|
+
const files = message.attachments ?? [];
|
|
24858
|
+
const said2 = files.flatMap((file3) => file3.transcript === void 0 ? [] : [file3.transcript]);
|
|
24859
|
+
const words4 = [
|
|
24860
|
+
message.text.trim(),
|
|
24861
|
+
...said2.map((each) => `(voice note: ${each})`),
|
|
24862
|
+
files.length === 0 ? "" : `(sent ${files.map((file3) => file3.filename).join(", ")})`
|
|
24863
|
+
].filter((part) => part !== "");
|
|
24864
|
+
const who = withRole(message.from.display_name, message.from);
|
|
24865
|
+
return `${who} said in the room, not to you: ${words4.join(" ")}`;
|
|
24866
|
+
}
|
|
24867
|
+
|
|
24827
24868
|
// node_modules/@chipschat/harness/dist/join.js
|
|
24828
24869
|
import { createHash } from "node:crypto";
|
|
24829
24870
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -25098,8 +25139,19 @@ async function redeem(url2, secret, options, send, machine) {
|
|
|
25098
25139
|
throw await refused(answered);
|
|
25099
25140
|
return await answered.json();
|
|
25100
25141
|
}
|
|
25142
|
+
function reaching(send, url2) {
|
|
25143
|
+
return async (input2, init) => {
|
|
25144
|
+
try {
|
|
25145
|
+
return await send(input2, init);
|
|
25146
|
+
} catch (error62) {
|
|
25147
|
+
if (error62 instanceof Error && error62.name === "AbortError")
|
|
25148
|
+
throw error62;
|
|
25149
|
+
throw new JoinError("unreachable", `Can't reach Chips at ${url2} from this computer. It has to be on the same network as Chips, on its tailnet, or Chips has to be at an address this computer can reach.`);
|
|
25150
|
+
}
|
|
25151
|
+
};
|
|
25152
|
+
}
|
|
25101
25153
|
function askOrRedeem(server, options) {
|
|
25102
|
-
const send = options.fetch ?? fetch;
|
|
25154
|
+
const send = reaching(options.fetch ?? fetch, server.url);
|
|
25103
25155
|
const machine = options.machine ?? hostname3();
|
|
25104
25156
|
return server.secret === void 0 ? ask(server.url, options, send, machine) : redeem(server.url, server.secret, options, send, machine);
|
|
25105
25157
|
}
|
|
@@ -25265,14 +25317,31 @@ async function sendAnswer(answer, options) {
|
|
|
25265
25317
|
}
|
|
25266
25318
|
|
|
25267
25319
|
// node_modules/@chipschat/harness/dist/prompt.js
|
|
25320
|
+
function takerOf(message, options) {
|
|
25321
|
+
return options.taker ?? (message.for?.length === 1 ? message.for[0] : void 0);
|
|
25322
|
+
}
|
|
25323
|
+
function othersBeside(message, taker) {
|
|
25324
|
+
return (message.room?.members ?? []).some((member) => member.kind === "agent" && member.participant_id !== taker);
|
|
25325
|
+
}
|
|
25326
|
+
function rulesFor(message, options = {}) {
|
|
25327
|
+
const taker = takerOf(message, options);
|
|
25328
|
+
const { project } = options;
|
|
25329
|
+
const rules = message.room?.rules ?? {};
|
|
25330
|
+
return [
|
|
25331
|
+
taker === void 0 ? void 0 : rules[taker],
|
|
25332
|
+
project === void 0 ? void 0 : renderProjectAsks(project, { othersInRoom: othersBeside(message, taker) }),
|
|
25333
|
+
options.sendsFiles === true ? sendingFiles({ project: project !== void 0 }) : void 0
|
|
25334
|
+
].filter((part) => part !== void 0 && part !== "").join("\n\n");
|
|
25335
|
+
}
|
|
25268
25336
|
function promptFor(message, options = {}) {
|
|
25269
|
-
const taker =
|
|
25337
|
+
const taker = takerOf(message, options);
|
|
25338
|
+
const held = options.rulesHeld === true;
|
|
25270
25339
|
const notes = message.room?.notes ?? {};
|
|
25271
|
-
const note = taker === void 0 ? void 0 : notes[taker];
|
|
25340
|
+
const note = taker === void 0 ? void 0 : held ? noteWithoutRules(message.room, taker) : notes[taker];
|
|
25272
25341
|
const who = options.sayWho === false ? "" : `${withRole(message.from.display_name, message.from)}: `;
|
|
25273
25342
|
const files = (message.attachments ?? []).map(renderAttachment);
|
|
25274
25343
|
const words4 = [`${who}${message.text}`, ...files].join("\n");
|
|
25275
|
-
const sending = options.sendsFiles === true ? sendingFiles({ project: options.project !== void 0 }) : void 0;
|
|
25344
|
+
const sending = options.sendsFiles === true && !held ? sendingFiles({ project: options.project !== void 0 }) : void 0;
|
|
25276
25345
|
const replying = renderReplyingTo(message.replying_to, {
|
|
25277
25346
|
sender: {
|
|
25278
25347
|
id: message.from.participant_id,
|
|
@@ -25284,8 +25353,7 @@ function promptFor(message, options = {}) {
|
|
|
25284
25353
|
at: message.ts,
|
|
25285
25354
|
zone: options.zone
|
|
25286
25355
|
});
|
|
25287
|
-
const
|
|
25288
|
-
const project = options.project === void 0 ? void 0 : renderProjectNote(options.project, { othersInRoom: others });
|
|
25356
|
+
const project = options.project === void 0 ? void 0 : held ? renderProjectFacts(options.project) : renderProjectNote(options.project, { othersInRoom: othersBeside(message, taker) });
|
|
25289
25357
|
return [
|
|
25290
25358
|
message.exchange?.note,
|
|
25291
25359
|
note,
|
|
@@ -25472,7 +25540,10 @@ function nodeOnPath(env = process.env) {
|
|
|
25472
25540
|
}
|
|
25473
25541
|
var pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
25474
25542
|
var LOAD_TRIES = 40;
|
|
25475
|
-
var runOf = (options) => options.run ?? ((command) =>
|
|
25543
|
+
var runOf = (options) => options.run ?? ((command) => execFileSync2(command[0], command.slice(1), {
|
|
25544
|
+
encoding: "utf8",
|
|
25545
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
25546
|
+
}));
|
|
25476
25547
|
var ours = (path) => !existsSync3(path) || readFileSync3(path, "utf8").includes(MARK);
|
|
25477
25548
|
function runOne(command, run2, retryMs) {
|
|
25478
25549
|
const tries = command[0] === "launchctl" && command[1] === "bootstrap" ? LOAD_TRIES : 1;
|
|
@@ -25495,6 +25566,24 @@ function runAll(commands, options) {
|
|
|
25495
25566
|
runOne(command, run2, options.retryMs ?? 250);
|
|
25496
25567
|
}
|
|
25497
25568
|
var uidOf = (options) => options.uid ?? process.getuid?.() ?? 0;
|
|
25569
|
+
function lingers(options) {
|
|
25570
|
+
const run2 = runOf(options);
|
|
25571
|
+
const uid = String(uidOf(options));
|
|
25572
|
+
try {
|
|
25573
|
+
const on = run2(["loginctl", "show-user", uid, "--property=Linger", "--value"]);
|
|
25574
|
+
if (typeof on === "string" && on.trim() === "yes")
|
|
25575
|
+
return true;
|
|
25576
|
+
} catch {
|
|
25577
|
+
}
|
|
25578
|
+
try {
|
|
25579
|
+
run2(["loginctl", "enable-linger", uid]);
|
|
25580
|
+
return true;
|
|
25581
|
+
} catch {
|
|
25582
|
+
return false;
|
|
25583
|
+
}
|
|
25584
|
+
}
|
|
25585
|
+
var sayOf = (options) => options.say ?? ((line2) => void process.stderr.write(`${line2}
|
|
25586
|
+
`));
|
|
25498
25587
|
function keepRunning(spec, options = {}) {
|
|
25499
25588
|
if (machineOf(options.machine).env["CHIPS_KEEP"] === "off")
|
|
25500
25589
|
return { state: "off" };
|
|
@@ -25509,6 +25598,9 @@ function keepRunning(spec, options = {}) {
|
|
|
25509
25598
|
writeFileSync2(file3.path, file3.contents, { mode: 384 });
|
|
25510
25599
|
chmodSync2(file3.path, 384);
|
|
25511
25600
|
runAll(startCommands(file3.kind, spec.label, file3.path, uidOf(options)), options);
|
|
25601
|
+
if (file3.kind === "systemd" && !lingers(options)) {
|
|
25602
|
+
sayOf(options)("It stops when you log out. To keep it running on a machine nobody logs in to, run this once: sudo loginctl enable-linger $USER");
|
|
25603
|
+
}
|
|
25512
25604
|
return { state: "kept", path: file3.path, logFile: file3.logFile };
|
|
25513
25605
|
}
|
|
25514
25606
|
function stopKeeping(label2, options = {}) {
|
|
@@ -27340,9 +27432,11 @@ function filesHere(project = false) {
|
|
|
27340
27432
|
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.`;
|
|
27341
27433
|
}
|
|
27342
27434
|
var FILES_HERE = filesHere();
|
|
27343
|
-
|
|
27435
|
+
var MARKDOWN_HERE = "Hermes's note that this API server's replies are plain text does not hold for these runs either: Chips shows your answer as Markdown, so use headings, lists, bold, tables and code blocks where they help a reader.";
|
|
27436
|
+
function instructionsFor(folder2, project = false, rules = "") {
|
|
27344
27437
|
const files = filesHere(project);
|
|
27345
|
-
|
|
27438
|
+
const here2 = folder2 === void 0 ? files : `${files} Your own folder is ${folder2}: make and keep your files there, and run your commands from there.`;
|
|
27439
|
+
return [here2, MARKDOWN_HERE, rules].filter((part) => part !== "").join("\n\n");
|
|
27346
27440
|
}
|
|
27347
27441
|
function freeFolder(root2, name2, taken) {
|
|
27348
27442
|
const slug = name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
@@ -27408,6 +27502,8 @@ var Bridge = class {
|
|
|
27408
27502
|
options;
|
|
27409
27503
|
agents = [];
|
|
27410
27504
|
rooms = /* @__PURE__ */ new Map();
|
|
27505
|
+
/** The turns going, by their Chips run, for what is said in their rooms while they work (Chips ADR 0106). */
|
|
27506
|
+
working = /* @__PURE__ */ new Map();
|
|
27411
27507
|
/** Every Chips agent of the harness's, configured here or not. */
|
|
27412
27508
|
ours = /* @__PURE__ */ new Set();
|
|
27413
27509
|
/** The harness's agents' names, by Chips id, for the folders named after them. */
|
|
@@ -27455,6 +27551,7 @@ var Bridge = class {
|
|
|
27455
27551
|
}
|
|
27456
27552
|
this.ours = /* @__PURE__ */ new Set([...known.map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
27457
27553
|
chips2.on("message.new", (message) => void this.hear(message));
|
|
27554
|
+
chips2.on("message.overheard", (message) => void this.overheard(message));
|
|
27458
27555
|
chips2.on("agent.asked", (asked) => {
|
|
27459
27556
|
this.making = this.making.then(() => this.make(asked)).catch((error62) => this.log(`a new agent went wrong: ${messageOf3(error62)}`));
|
|
27460
27557
|
});
|
|
@@ -28071,8 +28168,20 @@ ${hook.lines}`);
|
|
|
28071
28168
|
counted: 0,
|
|
28072
28169
|
approvals: /* @__PURE__ */ new Map(),
|
|
28073
28170
|
allowed: [],
|
|
28074
|
-
interrupted: false
|
|
28171
|
+
interrupted: false,
|
|
28172
|
+
overheard: []
|
|
28075
28173
|
};
|
|
28174
|
+
this.working.set(run2.id, turn);
|
|
28175
|
+
try {
|
|
28176
|
+
await this.take(turn, room, { message, speakerId, sayWho });
|
|
28177
|
+
} finally {
|
|
28178
|
+
this.working.delete(run2.id);
|
|
28179
|
+
}
|
|
28180
|
+
}
|
|
28181
|
+
/** A turn begun: Hermes's runs of it, from the first words to its end. */
|
|
28182
|
+
async take(turn, room, { message, speakerId, sayWho }) {
|
|
28183
|
+
const { agent } = room;
|
|
28184
|
+
const { run: run2 } = turn;
|
|
28076
28185
|
run2.on("cancel", () => {
|
|
28077
28186
|
turn.cancelled = true;
|
|
28078
28187
|
if (turn.hermesRun !== "") void agent.client.stop(turn.hermesRun).catch(() => void 0);
|
|
@@ -28091,7 +28200,8 @@ ${hook.lines}`);
|
|
|
28091
28200
|
const name2 = speakerId === void 0 ? agent.config.name : await this.nameOf(speakerId, agent.config.name);
|
|
28092
28201
|
turn.folder = this.folderOf(agent, speakerId ?? agent.agentId, name2);
|
|
28093
28202
|
const project = await this.projectFor(turn, room.spaceId, speakerId ?? agent.agentId, name2);
|
|
28094
|
-
|
|
28203
|
+
turn.rules = rulesFor(message, { taker: speakerId, project });
|
|
28204
|
+
let text = promptFor(message, { taker: speakerId, sayWho, project, rulesHeld: true });
|
|
28095
28205
|
for (let round = 0; text !== void 0 && !turn.cancelled && !run2.over; round += 1) {
|
|
28096
28206
|
const last = await this.once(turn, session, text, `${run2.id}:${round}`);
|
|
28097
28207
|
if (last === void 0) return;
|
|
@@ -28117,19 +28227,53 @@ ${hook.lines}`);
|
|
|
28117
28227
|
sessionId: session,
|
|
28118
28228
|
...this.runModel(turn),
|
|
28119
28229
|
key,
|
|
28120
|
-
instructions: instructionsFor(turn.folder, turn.project !== void 0)
|
|
28230
|
+
instructions: instructionsFor(turn.folder, turn.project !== void 0, turn.rules)
|
|
28121
28231
|
});
|
|
28122
28232
|
} catch (error62) {
|
|
28123
28233
|
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${messageOf3(error62)}`, true);
|
|
28124
28234
|
return void 0;
|
|
28125
28235
|
}
|
|
28126
28236
|
if (turn.cancelled) await agent.client.stop(turn.hermesRun).catch(() => void 0);
|
|
28237
|
+
let last;
|
|
28127
28238
|
try {
|
|
28128
|
-
|
|
28129
|
-
return last ?? await this.endOf(turn);
|
|
28239
|
+
last = await agent.client.events(turn.hermesRun, (event) => this.heard(turn, event)) ?? await this.endOf(turn);
|
|
28130
28240
|
} catch (error62) {
|
|
28131
28241
|
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${messageOf3(error62)}`);
|
|
28132
|
-
|
|
28242
|
+
last = await this.endOf(turn);
|
|
28243
|
+
}
|
|
28244
|
+
await this.toldWhileWorking(turn, last);
|
|
28245
|
+
return last;
|
|
28246
|
+
}
|
|
28247
|
+
/**
|
|
28248
|
+
* Words said in a room, not to the harness, while its agents work there (Chips ADRs 0105, 0106):
|
|
28249
|
+
* each turn Chips names hears them as a steer of its Hermes run, which Hermes gives the model
|
|
28250
|
+
* after the step it is on. A run that won't take a steer is ending, and its agent's next turn is
|
|
28251
|
+
* handed the words.
|
|
28252
|
+
*/
|
|
28253
|
+
async overheard(message) {
|
|
28254
|
+
const line2 = overheardLine(message);
|
|
28255
|
+
for (const { runId } of workingTurnsOf(message)) {
|
|
28256
|
+
const turn = this.working.get(runId);
|
|
28257
|
+
if (turn === void 0 || turn.run.over || turn.hermesRun === "") continue;
|
|
28258
|
+
try {
|
|
28259
|
+
await turn.agent.client.steer(turn.hermesRun, line2);
|
|
28260
|
+
turn.overheard.push({ messageId: message.message_id, line: line2 });
|
|
28261
|
+
} catch {
|
|
28262
|
+
}
|
|
28263
|
+
}
|
|
28264
|
+
}
|
|
28265
|
+
/**
|
|
28266
|
+
* Which of the words steered into a run it heard: those Hermes gave it before it completed, not
|
|
28267
|
+
* those it kept back as `pending_steer`, nor any in a run that failed or was stopped. Chips is
|
|
28268
|
+
* told of each, and the room shows the agent heard it while working (Chips ADR 0106).
|
|
28269
|
+
*/
|
|
28270
|
+
async toldWhileWorking(turn, last) {
|
|
28271
|
+
const told = turn.overheard.splice(0);
|
|
28272
|
+
if (last?.event !== "run.completed") return;
|
|
28273
|
+
const kept = typeof last["pending_steer"] === "string" ? last["pending_steer"] : "";
|
|
28274
|
+
for (const { messageId, line: line2 } of told) {
|
|
28275
|
+
if (kept.includes(line2)) continue;
|
|
28276
|
+
await turn.run.heard(messageId).catch((error62) => this.log(`couldn't say a turn heard ${messageId}: ${messageOf3(error62)}`));
|
|
28133
28277
|
}
|
|
28134
28278
|
}
|
|
28135
28279
|
/** The end of a run whose stream was lost: its state read until it is over, as a terminal event. */
|
|
@@ -28672,6 +28816,10 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
|
|
|
28672
28816
|
}
|
|
28673
28817
|
}
|
|
28674
28818
|
|
|
28819
|
+
// src/version.ts
|
|
28820
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
28821
|
+
var VERSION = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
28822
|
+
|
|
28675
28823
|
// src/main.ts
|
|
28676
28824
|
var home = (path) => path.startsWith("~/") ? join13(homedir8(), path.slice(2)) : path;
|
|
28677
28825
|
var link = process.env["CHIPS_JOIN_LINK"] || void 0;
|
|
@@ -28680,7 +28828,7 @@ stopIfAsked(LABEL);
|
|
|
28680
28828
|
var configPath = resolve6(home(words3[0] ?? "~/.chips-hermes/config.json"));
|
|
28681
28829
|
var folder = dirname6(configPath);
|
|
28682
28830
|
var statePath = join13(folder, "sessions.json");
|
|
28683
|
-
var state = existsSync11(statePath) ? JSON.parse(
|
|
28831
|
+
var state = existsSync11(statePath) ? JSON.parse(readFileSync12(statePath, "utf8")) : { sessions: {} };
|
|
28684
28832
|
var saveState = (next) => {
|
|
28685
28833
|
mkdirSync6(folder, { recursive: true, mode: 448 });
|
|
28686
28834
|
writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
|
|
@@ -28741,7 +28889,7 @@ var joined = await join3({
|
|
|
28741
28889
|
link,
|
|
28742
28890
|
url: process.env["CHIPS_URL"],
|
|
28743
28891
|
name,
|
|
28744
|
-
software:
|
|
28892
|
+
software: `chips-hermes ${VERSION}`,
|
|
28745
28893
|
wants: WANTS,
|
|
28746
28894
|
onCode: (code) => log(`Allow this harness in Chips: ${code}`)
|
|
28747
28895
|
}).catch(refused2);
|
|
@@ -28759,7 +28907,7 @@ var restart = restarter({
|
|
|
28759
28907
|
var chips = await connect({
|
|
28760
28908
|
url: joined.url,
|
|
28761
28909
|
token: joined.token,
|
|
28762
|
-
client: { name: "chips-hermes", version:
|
|
28910
|
+
client: { name: "chips-hermes", version: VERSION },
|
|
28763
28911
|
store: fileStore(join13(folder, "chips-state.json")),
|
|
28764
28912
|
onRestart: restart,
|
|
28765
28913
|
settings: settingsFor(() => bridge)
|
package/package.json
CHANGED