@chipschat/opencode 1.0.6 → 1.0.9
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 +74 -11
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +1100 -73
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -8,9 +8,9 @@ var __export = (target, all) => {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// src/main.ts
|
|
11
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as
|
|
12
|
-
import { homedir as
|
|
13
|
-
import { dirname as
|
|
11
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
12
|
+
import { homedir as homedir7 } from "node:os";
|
|
13
|
+
import { dirname as dirname5, join as join10, resolve as resolve4 } from "node:path";
|
|
14
14
|
|
|
15
15
|
// node_modules/zod/v4/classic/external.js
|
|
16
16
|
var external_exports = {};
|
|
@@ -4871,10 +4871,10 @@ function partPattern(schema) {
|
|
|
4871
4871
|
const own2 = schema._zod.pattern?.source;
|
|
4872
4872
|
const inner = def.innerType ?? schema._zod.innerType;
|
|
4873
4873
|
if (inner) {
|
|
4874
|
-
const
|
|
4874
|
+
const before2 = inner._zod.pattern?.source;
|
|
4875
4875
|
const after = partPattern(inner);
|
|
4876
|
-
if (own2 &&
|
|
4877
|
-
return own2.replace(cleanRegex(
|
|
4876
|
+
if (own2 && before2 && after && after !== before2) {
|
|
4877
|
+
return own2.replace(cleanRegex(before2), () => cleanRegex(after));
|
|
4878
4878
|
}
|
|
4879
4879
|
return own2;
|
|
4880
4880
|
}
|
|
@@ -19943,8 +19943,9 @@ var policyRule = external_exports.object({
|
|
|
19943
19943
|
external: external_exports.boolean().optional(),
|
|
19944
19944
|
irreversible: external_exports.boolean().optional(),
|
|
19945
19945
|
money_usd_over: external_exports.number().nonnegative().optional(),
|
|
19946
|
-
risk: external_exports.array(riskClass).max(8).optional()
|
|
19947
|
-
|
|
19946
|
+
risk: external_exports.array(riskClass).max(8).optional(),
|
|
19947
|
+
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.")
|
|
19948
|
+
}).optional().describe("Extra conditions on the action's flags, and for a command, how its parts start."),
|
|
19948
19949
|
decision: external_exports.enum(["allow", "ask", "deny"]).describe("What to do when the rule matches."),
|
|
19949
19950
|
approvers: external_exports.array(approverRef).max(16).optional().describe("Who may answer, when the decision is `ask`."),
|
|
19950
19951
|
reason: external_exports.string().max(400).optional().describe("Shown to the harness, and recorded in the audit log."),
|
|
@@ -20463,9 +20464,17 @@ var roomIdle = external_exports.object({
|
|
|
20463
20464
|
var roomNote = external_exports.object({
|
|
20464
20465
|
members: external_exports.array(roomMember).max(500).describe("The room's people, then its agents, each by name, anybody removed left out."),
|
|
20465
20466
|
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."),
|
|
20467
|
+
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."),
|
|
20466
20468
|
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."),
|
|
20467
20469
|
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).")
|
|
20468
20470
|
});
|
|
20471
|
+
function noteWithoutRules(room, taker) {
|
|
20472
|
+
const note = room?.notes[taker];
|
|
20473
|
+
const rules = room?.rules?.[taker];
|
|
20474
|
+
if (note === void 0 || rules === void 0 || !note.endsWith(rules))
|
|
20475
|
+
return note;
|
|
20476
|
+
return note.slice(0, note.length - rules.length).trimEnd();
|
|
20477
|
+
}
|
|
20469
20478
|
var MONTHS = [
|
|
20470
20479
|
"January",
|
|
20471
20480
|
"February",
|
|
@@ -20485,8 +20494,8 @@ var MONTHS = [
|
|
|
20485
20494
|
var CONTEXT_CEILING = { lines: 200, chars: 32e3 };
|
|
20486
20495
|
var CONTEXT_HEADING = "Said in this space since you last spoke:";
|
|
20487
20496
|
var contextBudget = external_exports.object({
|
|
20488
|
-
lines: external_exports.int().nonnegative().optional().describe("The most lines to be handed,
|
|
20489
|
-
chars: external_exports.int().nonnegative().optional().describe("The most characters those lines may run to, as `Name: words` one per line,
|
|
20497
|
+
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."),
|
|
20498
|
+
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.")
|
|
20490
20499
|
}).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.");
|
|
20491
20500
|
var contextLine = external_exports.object({
|
|
20492
20501
|
seq: external_exports.int().positive().describe("Where in the log it happened."),
|
|
@@ -20698,12 +20707,14 @@ var contextAnswer = {
|
|
|
20698
20707
|
message: external_exports.string().min(1).max(300).optional().describe("What went wrong, for the person.")
|
|
20699
20708
|
}).optional().describe("An edit not made. The copy beside it is the file as it is now.")
|
|
20700
20709
|
};
|
|
20710
|
+
var readNote = external_exports.string().min(1).max(300).describe("When the agent reads these, in the harness's words: `Hermes reads these when a conversation starts.` Absent: at the start of every turn.");
|
|
20701
20711
|
function uniqueNames(files) {
|
|
20702
20712
|
return new Set(files.map((file3) => file3.name)).size === files.length;
|
|
20703
20713
|
}
|
|
20704
20714
|
var agentContextPut = external_exports.object({
|
|
20705
20715
|
files: external_exports.array(contextFile).max(CONTEXT_FILES_SHOWN).refine(uniqueNames, { message: "Each file is named once." }).describe("The agent's files, in the order its page shows them: eight at most."),
|
|
20706
20716
|
not_shown: external_exports.array(notShownFile).max(CONTEXT_NOT_SHOWN_MAX).optional(),
|
|
20717
|
+
read_note: readNote.optional(),
|
|
20707
20718
|
...contextAnswer
|
|
20708
20719
|
}).describe("`PUT /v1/agents/:id/context`: a harness's copy of one of its agents' files.");
|
|
20709
20720
|
var harnessContextPut = external_exports.object({
|
|
@@ -20736,6 +20747,7 @@ var agentContextView = external_exports.object({
|
|
|
20736
20747
|
harness_id: id("harness"),
|
|
20737
20748
|
files: external_exports.array(contextFile),
|
|
20738
20749
|
not_shown: external_exports.array(notShownFile),
|
|
20750
|
+
read_note: readNote.optional(),
|
|
20739
20751
|
read_at: isoTimestamp.describe("When the harness last sent its copy."),
|
|
20740
20752
|
away: external_exports.boolean().describe("No socket of the harness hears edits now: read, but not saved.")
|
|
20741
20753
|
}).describe("`GET /v1/agents/:id/context`, for the harness's owner.");
|
|
@@ -20863,7 +20875,7 @@ var messageNew = defineEvent({
|
|
|
20863
20875
|
summary: "Someone said something, with any attachments as signed URLs the harness can fetch.",
|
|
20864
20876
|
requiresRun: false,
|
|
20865
20877
|
payload: deliveredMessage.extend({
|
|
20866
|
-
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`:
|
|
20878
|
+
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."),
|
|
20867
20879
|
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."),
|
|
20868
20880
|
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."),
|
|
20869
20881
|
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."),
|
|
@@ -20913,6 +20925,7 @@ var approvalResolved = defineEvent({
|
|
|
20913
20925
|
approval_id: id("approval"),
|
|
20914
20926
|
decision: external_exports.enum(["approved", "edited", "denied", "expired"]).describe("`expired` means nobody answered and the fallback applied."),
|
|
20915
20927
|
scope: external_exports.enum(["once", "session", "always"]).default("once").describe("How far the answer reaches. `always` becomes a policy rule."),
|
|
20928
|
+
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."),
|
|
20916
20929
|
updated_input: external_exports.unknown().optional().describe("The edited action input, when the decision is `edited`."),
|
|
20917
20930
|
reason: external_exports.string().max(4e3).optional().describe("Why. The harness gets to read this."),
|
|
20918
20931
|
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."),
|
|
@@ -21177,6 +21190,10 @@ function namesInWords(names) {
|
|
|
21177
21190
|
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
21178
21191
|
}
|
|
21179
21192
|
function renderProjectNote(facts, options) {
|
|
21193
|
+
return `${renderProjectFacts(facts)}
|
|
21194
|
+
${renderProjectAsks(facts, options)}`;
|
|
21195
|
+
}
|
|
21196
|
+
function renderProjectFacts(facts) {
|
|
21180
21197
|
const files = `${facts.files} file${facts.files === 1 ? "" : "s"}`;
|
|
21181
21198
|
const lines2 = [
|
|
21182
21199
|
`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"}.`
|
|
@@ -21194,9 +21211,9 @@ function renderProjectNote(facts, options) {
|
|
|
21194
21211
|
for (const key of facts.keys) {
|
|
21195
21212
|
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.`);
|
|
21196
21213
|
}
|
|
21197
|
-
return
|
|
21214
|
+
return lines2.join("\n");
|
|
21198
21215
|
}
|
|
21199
|
-
function
|
|
21216
|
+
function renderProjectAsks(facts, options) {
|
|
21200
21217
|
const lines2 = [
|
|
21201
21218
|
"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.",
|
|
21202
21219
|
// Sent with the answer too, a project's file became a room file of its own, with versions of
|
|
@@ -21217,7 +21234,7 @@ function asks(facts, options) {
|
|
|
21217
21234
|
if (facts.pages === true) {
|
|
21218
21235
|
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.");
|
|
21219
21236
|
}
|
|
21220
|
-
return lines2;
|
|
21237
|
+
return lines2.join("\n");
|
|
21221
21238
|
}
|
|
21222
21239
|
|
|
21223
21240
|
// node_modules/@chipschat/protocol/dist/src/events/harness-to-chips.js
|
|
@@ -21869,7 +21886,7 @@ var membershipRemoved = defineEvent({
|
|
|
21869
21886
|
payload: external_exports.object({
|
|
21870
21887
|
participant_id: external_exports.string().min(1).max(120).describe("Who left."),
|
|
21871
21888
|
removed_by: external_exports.string().min(1).max(120).optional().describe("Who removed them, if not themselves."),
|
|
21872
|
-
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."),
|
|
21889
|
+
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."),
|
|
21873
21890
|
moved_to: id("space").optional().describe("For an agent moved away: the room it went to, so this room can say so.")
|
|
21874
21891
|
})
|
|
21875
21892
|
});
|
|
@@ -22178,14 +22195,14 @@ var personRemoved = defineEvent({
|
|
|
22178
22195
|
type: "person.removed",
|
|
22179
22196
|
direction: "workspace",
|
|
22180
22197
|
sender: "chips",
|
|
22181
|
-
summary: "A person was removed from the workspace: signed out everywhere, out of every room, and the harnesses they owned handed
|
|
22198
|
+
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.",
|
|
22182
22199
|
requiresRun: false,
|
|
22183
22200
|
payload: external_exports.object({
|
|
22184
22201
|
participant_id: id("person").describe("Who was removed."),
|
|
22185
22202
|
display_name: external_exports.string().min(1).max(120).describe("What they were called when they were removed, so the audit needs no lookup."),
|
|
22186
|
-
removed_by: id("person").describe("The owner or admin who removed them."),
|
|
22203
|
+
removed_by: id("person").describe("The owner or admin who removed them, or they themselves when they deleted their account (ADR 0243)."),
|
|
22187
22204
|
spaces: external_exports.array(id("space")).max(1e3).optional().describe("The rooms they were taken out of, each with its own `membership.removed`."),
|
|
22188
|
-
harnesses: external_exports.array(id("harness")).max(1e3).optional().describe("The harnesses they owned, handed to `removed_by
|
|
22205
|
+
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.")
|
|
22189
22206
|
})
|
|
22190
22207
|
});
|
|
22191
22208
|
var joinWho = {
|
|
@@ -22401,6 +22418,89 @@ var EVENT_TYPES = EVENTS.map((event) => event.type);
|
|
|
22401
22418
|
var byType = new Map(EVENTS.map((event) => [event.type, event]));
|
|
22402
22419
|
var schemas = new Map(EVENTS.map((event) => [event.type, eventSchema(event)]));
|
|
22403
22420
|
|
|
22421
|
+
// node_modules/@chipschat/protocol/dist/src/names.js
|
|
22422
|
+
var LOOK_LIKE = {
|
|
22423
|
+
A: "\u0410\u0391",
|
|
22424
|
+
// Cyrillic А, Greek Α
|
|
22425
|
+
B: "\u0412\u0392",
|
|
22426
|
+
// Cyrillic В, Greek Β
|
|
22427
|
+
C: "\u0421",
|
|
22428
|
+
// Cyrillic С
|
|
22429
|
+
E: "\u0415\u0395",
|
|
22430
|
+
// Cyrillic Е, Greek Ε
|
|
22431
|
+
H: "\u041D\u0397",
|
|
22432
|
+
// Cyrillic Н, Greek Η
|
|
22433
|
+
I: "\u0406\u04C0\u0399",
|
|
22434
|
+
// Cyrillic І and Ӏ, Greek Ι
|
|
22435
|
+
J: "\u0408",
|
|
22436
|
+
// Cyrillic Ј
|
|
22437
|
+
K: "\u041A\u039A",
|
|
22438
|
+
// Cyrillic К, Greek Κ
|
|
22439
|
+
M: "\u041C\u039C",
|
|
22440
|
+
// Cyrillic М, Greek Μ
|
|
22441
|
+
N: "\u039D",
|
|
22442
|
+
// Greek Ν
|
|
22443
|
+
O: "\u041E\u039F",
|
|
22444
|
+
// Cyrillic О, Greek Ο
|
|
22445
|
+
P: "\u0420\u03A1",
|
|
22446
|
+
// Cyrillic Р, Greek Ρ
|
|
22447
|
+
Q: "\u051A",
|
|
22448
|
+
// Cyrillic Ԛ
|
|
22449
|
+
S: "\u0405",
|
|
22450
|
+
// Cyrillic Ѕ
|
|
22451
|
+
T: "\u0422\u03A4",
|
|
22452
|
+
// Cyrillic Т, Greek Τ
|
|
22453
|
+
W: "\u051C",
|
|
22454
|
+
// Cyrillic Ԝ
|
|
22455
|
+
X: "\u0425\u03A7",
|
|
22456
|
+
// Cyrillic Х, Greek Χ
|
|
22457
|
+
Y: "\u0423\u04AE\u03A5",
|
|
22458
|
+
// Cyrillic У and Ү, Greek Υ
|
|
22459
|
+
Z: "\u0396",
|
|
22460
|
+
// Greek Ζ
|
|
22461
|
+
a: "\u0430\u03B1",
|
|
22462
|
+
// Cyrillic а, Greek α
|
|
22463
|
+
c: "\u0441",
|
|
22464
|
+
// Cyrillic с
|
|
22465
|
+
d: "\u0501",
|
|
22466
|
+
// Cyrillic ԁ
|
|
22467
|
+
e: "\u0435",
|
|
22468
|
+
// Cyrillic е
|
|
22469
|
+
h: "\u04BB",
|
|
22470
|
+
// Cyrillic һ
|
|
22471
|
+
i: "\u0456\u03B9\u0131",
|
|
22472
|
+
// Cyrillic і, Greek ι, Latin dotless ı
|
|
22473
|
+
j: "\u0458",
|
|
22474
|
+
// Cyrillic ј
|
|
22475
|
+
k: "\u03BA",
|
|
22476
|
+
// Greek κ
|
|
22477
|
+
l: "\u04CF",
|
|
22478
|
+
// Cyrillic ӏ
|
|
22479
|
+
o: "\u043E\u03BF",
|
|
22480
|
+
// Cyrillic о, Greek ο
|
|
22481
|
+
p: "\u0440\u03C1",
|
|
22482
|
+
// Cyrillic р, Greek ρ
|
|
22483
|
+
q: "\u051B",
|
|
22484
|
+
// Cyrillic ԛ
|
|
22485
|
+
s: "\u0455",
|
|
22486
|
+
// Cyrillic ѕ
|
|
22487
|
+
u: "\u03C5",
|
|
22488
|
+
// Greek υ
|
|
22489
|
+
v: "\u03BD",
|
|
22490
|
+
// Greek ν
|
|
22491
|
+
w: "\u051D",
|
|
22492
|
+
// Cyrillic ԝ
|
|
22493
|
+
x: "\u0445\u03C7",
|
|
22494
|
+
// Cyrillic х, Greek χ
|
|
22495
|
+
y: "\u0443\u04AF"
|
|
22496
|
+
// Cyrillic у and ү
|
|
22497
|
+
};
|
|
22498
|
+
var LATIN_OF = new Map(Object.entries(LOOK_LIKE).flatMap(([latin, others]) => [...others].map((other) => [other, latin])));
|
|
22499
|
+
var HIDDEN = "\\p{Cc}\\p{Cf}\\u034f\\u115f\\u1160\\u3164\\uffa0\\u2800";
|
|
22500
|
+
var UNSEEN = new RegExp(`[${HIDDEN}\\ufe00-\\ufe0f\\u{e0100}-\\u{e01ef}]`, "gu");
|
|
22501
|
+
var HIDDEN_ONE = new RegExp(`[${HIDDEN}]`, "u");
|
|
22502
|
+
var ALPHABETS = [new RegExp("\\p{Script=Latin}", "u"), new RegExp("\\p{Script=Cyrillic}", "u"), new RegExp("\\p{Script=Greek}", "u")];
|
|
22503
|
+
|
|
22404
22504
|
// node_modules/@chipschat/protocol/dist/src/first-names.js
|
|
22405
22505
|
var AGENT_FIRST_NAMES = [
|
|
22406
22506
|
"Abel Aldo Amir Amos Anders Ansel Anton Ari Arlo Arne Aron Arvid Asher Avi Axel Aziz",
|
|
@@ -22448,7 +22548,7 @@ var clientHelloFrame = external_exports.object({
|
|
|
22448
22548
|
})).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."),
|
|
22449
22549
|
since: external_exports.int().nonnegative().optional().describe("Not read by Chips: a stream resumes from `subscribe`'s `since`."),
|
|
22450
22550
|
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."),
|
|
22451
|
-
context: contextBudget.optional().describe("How much of a room a harness is handed with each message for it (ADR 0088):
|
|
22551
|
+
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.")
|
|
22452
22552
|
}).describe("The first frame a client sends.");
|
|
22453
22553
|
var subscribeFrame = external_exports.object({
|
|
22454
22554
|
frame: external_exports.literal("subscribe"),
|
|
@@ -25010,8 +25110,19 @@ async function redeem(url2, secret, options, send, machine) {
|
|
|
25010
25110
|
throw await refused(answered);
|
|
25011
25111
|
return await answered.json();
|
|
25012
25112
|
}
|
|
25113
|
+
function reaching(send, url2) {
|
|
25114
|
+
return async (input2, init) => {
|
|
25115
|
+
try {
|
|
25116
|
+
return await send(input2, init);
|
|
25117
|
+
} catch (error62) {
|
|
25118
|
+
if (error62 instanceof Error && error62.name === "AbortError")
|
|
25119
|
+
throw error62;
|
|
25120
|
+
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.`);
|
|
25121
|
+
}
|
|
25122
|
+
};
|
|
25123
|
+
}
|
|
25013
25124
|
function askOrRedeem(server2, options) {
|
|
25014
|
-
const send = options.fetch ?? fetch;
|
|
25125
|
+
const send = reaching(options.fetch ?? fetch, server2.url);
|
|
25015
25126
|
const machine = options.machine ?? hostname3();
|
|
25016
25127
|
return server2.secret === void 0 ? ask(server2.url, options, send, machine) : redeem(server2.url, server2.secret, options, send, machine);
|
|
25017
25128
|
}
|
|
@@ -25177,14 +25288,31 @@ async function sendAnswer(answer, options) {
|
|
|
25177
25288
|
}
|
|
25178
25289
|
|
|
25179
25290
|
// node_modules/@chipschat/harness/dist/prompt.js
|
|
25291
|
+
function takerOf(message, options) {
|
|
25292
|
+
return options.taker ?? (message.for?.length === 1 ? message.for[0] : void 0);
|
|
25293
|
+
}
|
|
25294
|
+
function othersBeside(message, taker) {
|
|
25295
|
+
return (message.room?.members ?? []).some((member) => member.kind === "agent" && member.participant_id !== taker);
|
|
25296
|
+
}
|
|
25297
|
+
function rulesFor(message, options = {}) {
|
|
25298
|
+
const taker = takerOf(message, options);
|
|
25299
|
+
const { project } = options;
|
|
25300
|
+
const rules = message.room?.rules ?? {};
|
|
25301
|
+
return [
|
|
25302
|
+
taker === void 0 ? void 0 : rules[taker],
|
|
25303
|
+
project === void 0 ? void 0 : renderProjectAsks(project, { othersInRoom: othersBeside(message, taker) }),
|
|
25304
|
+
options.sendsFiles === true ? sendingFiles({ project: project !== void 0 }) : void 0
|
|
25305
|
+
].filter((part) => part !== void 0 && part !== "").join("\n\n");
|
|
25306
|
+
}
|
|
25180
25307
|
function promptFor(message, options = {}) {
|
|
25181
|
-
const taker =
|
|
25308
|
+
const taker = takerOf(message, options);
|
|
25309
|
+
const held = options.rulesHeld === true;
|
|
25182
25310
|
const notes = message.room?.notes ?? {};
|
|
25183
|
-
const note = taker === void 0 ? void 0 : notes[taker];
|
|
25311
|
+
const note = taker === void 0 ? void 0 : held ? noteWithoutRules(message.room, taker) : notes[taker];
|
|
25184
25312
|
const who = options.sayWho === false ? "" : `${withRole(message.from.display_name, message.from)}: `;
|
|
25185
25313
|
const files = (message.attachments ?? []).map(renderAttachment);
|
|
25186
25314
|
const words4 = [`${who}${message.text}`, ...files].join("\n");
|
|
25187
|
-
const sending = options.sendsFiles === true ? sendingFiles({ project: options.project !== void 0 }) : void 0;
|
|
25315
|
+
const sending = options.sendsFiles === true && !held ? sendingFiles({ project: options.project !== void 0 }) : void 0;
|
|
25188
25316
|
const replying = renderReplyingTo(message.replying_to, {
|
|
25189
25317
|
sender: {
|
|
25190
25318
|
id: message.from.participant_id,
|
|
@@ -25196,8 +25324,7 @@ function promptFor(message, options = {}) {
|
|
|
25196
25324
|
at: message.ts,
|
|
25197
25325
|
zone: options.zone
|
|
25198
25326
|
});
|
|
25199
|
-
const
|
|
25200
|
-
const project = options.project === void 0 ? void 0 : renderProjectNote(options.project, { othersInRoom: others });
|
|
25327
|
+
const project = options.project === void 0 ? void 0 : held ? renderProjectFacts(options.project) : renderProjectNote(options.project, { othersInRoom: othersBeside(message, taker) });
|
|
25201
25328
|
return [
|
|
25202
25329
|
message.exchange?.note,
|
|
25203
25330
|
note,
|
|
@@ -25209,6 +25336,24 @@ function promptFor(message, options = {}) {
|
|
|
25209
25336
|
].filter((part) => part !== void 0 && part !== "").join("\n\n");
|
|
25210
25337
|
}
|
|
25211
25338
|
|
|
25339
|
+
// node_modules/@chipschat/harness/dist/rules.js
|
|
25340
|
+
var SessionRules = class {
|
|
25341
|
+
held = /* @__PURE__ */ new Map();
|
|
25342
|
+
/** Whether `session` holds these rules: told them word for word, or there are none to hold. */
|
|
25343
|
+
holds(session, rules) {
|
|
25344
|
+
return rules === "" || this.held.get(session) === rules;
|
|
25345
|
+
}
|
|
25346
|
+
/** That `session` was told these rules: its turn that said them was given to it. */
|
|
25347
|
+
told(session, rules) {
|
|
25348
|
+
if (rules !== "")
|
|
25349
|
+
this.held.set(session, rules);
|
|
25350
|
+
}
|
|
25351
|
+
/** That `session` no longer holds what it was told: it compacted, or it is gone. */
|
|
25352
|
+
forget(session) {
|
|
25353
|
+
this.held.delete(session);
|
|
25354
|
+
}
|
|
25355
|
+
};
|
|
25356
|
+
|
|
25212
25357
|
// node_modules/@chipschat/harness/dist/service.js
|
|
25213
25358
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
25214
25359
|
import { accessSync, chmodSync as chmodSync2, constants, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -25384,7 +25529,10 @@ function nodeOnPath(env = process.env) {
|
|
|
25384
25529
|
}
|
|
25385
25530
|
var pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
25386
25531
|
var LOAD_TRIES = 40;
|
|
25387
|
-
var runOf = (options) => options.run ?? ((command) =>
|
|
25532
|
+
var runOf = (options) => options.run ?? ((command) => execFileSync2(command[0], command.slice(1), {
|
|
25533
|
+
encoding: "utf8",
|
|
25534
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
25535
|
+
}));
|
|
25388
25536
|
var ours = (path) => !existsSync3(path) || readFileSync3(path, "utf8").includes(MARK);
|
|
25389
25537
|
function runOne(command, run2, retryMs) {
|
|
25390
25538
|
const tries = command[0] === "launchctl" && command[1] === "bootstrap" ? LOAD_TRIES : 1;
|
|
@@ -25407,6 +25555,24 @@ function runAll(commands, options) {
|
|
|
25407
25555
|
runOne(command, run2, options.retryMs ?? 250);
|
|
25408
25556
|
}
|
|
25409
25557
|
var uidOf = (options) => options.uid ?? process.getuid?.() ?? 0;
|
|
25558
|
+
function lingers(options) {
|
|
25559
|
+
const run2 = runOf(options);
|
|
25560
|
+
const uid = String(uidOf(options));
|
|
25561
|
+
try {
|
|
25562
|
+
const on = run2(["loginctl", "show-user", uid, "--property=Linger", "--value"]);
|
|
25563
|
+
if (typeof on === "string" && on.trim() === "yes")
|
|
25564
|
+
return true;
|
|
25565
|
+
} catch {
|
|
25566
|
+
}
|
|
25567
|
+
try {
|
|
25568
|
+
run2(["loginctl", "enable-linger", uid]);
|
|
25569
|
+
return true;
|
|
25570
|
+
} catch {
|
|
25571
|
+
return false;
|
|
25572
|
+
}
|
|
25573
|
+
}
|
|
25574
|
+
var sayOf = (options) => options.say ?? ((line) => void process.stderr.write(`${line}
|
|
25575
|
+
`));
|
|
25410
25576
|
function keepRunning(spec, options = {}) {
|
|
25411
25577
|
if (machineOf(options.machine).env["CHIPS_KEEP"] === "off")
|
|
25412
25578
|
return { state: "off" };
|
|
@@ -25421,6 +25587,9 @@ function keepRunning(spec, options = {}) {
|
|
|
25421
25587
|
writeFileSync2(file3.path, file3.contents, { mode: 384 });
|
|
25422
25588
|
chmodSync2(file3.path, 384);
|
|
25423
25589
|
runAll(startCommands(file3.kind, spec.label, file3.path, uidOf(options)), options);
|
|
25590
|
+
if (file3.kind === "systemd" && !lingers(options)) {
|
|
25591
|
+
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");
|
|
25592
|
+
}
|
|
25424
25593
|
return { state: "kept", path: file3.path, logFile: file3.logFile };
|
|
25425
25594
|
}
|
|
25426
25595
|
function stopKeeping(label2, options = {}) {
|
|
@@ -25467,7 +25636,270 @@ function fileStore(path) {
|
|
|
25467
25636
|
|
|
25468
25637
|
// src/bridge.ts
|
|
25469
25638
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
25470
|
-
import {
|
|
25639
|
+
import { homedir as homedir4 } from "node:os";
|
|
25640
|
+
import { join as join6 } from "node:path";
|
|
25641
|
+
|
|
25642
|
+
// src/context.ts
|
|
25643
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
25644
|
+
import { globSync, readFileSync as readFileSync5, statSync } from "node:fs";
|
|
25645
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
25646
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, relative, sep as sep2 } from "node:path";
|
|
25647
|
+
var AGENTS_MD = "AGENTS.md";
|
|
25648
|
+
function placesOf(env, home3) {
|
|
25649
|
+
const config3 = env["OPENCODE_CONFIG_DIR"] || join5(env["XDG_CONFIG_HOME"] || join5(home3, ".config"), "opencode");
|
|
25650
|
+
return { home: home3, config: config3 };
|
|
25651
|
+
}
|
|
25652
|
+
function hashOf(text2) {
|
|
25653
|
+
return createHash2("sha256").update(text2, "utf8").digest("hex");
|
|
25654
|
+
}
|
|
25655
|
+
function isFile(path) {
|
|
25656
|
+
try {
|
|
25657
|
+
return statSync(path).isFile();
|
|
25658
|
+
} catch {
|
|
25659
|
+
return false;
|
|
25660
|
+
}
|
|
25661
|
+
}
|
|
25662
|
+
function textOf(path) {
|
|
25663
|
+
try {
|
|
25664
|
+
return readFileSync5(path, "utf8");
|
|
25665
|
+
} catch {
|
|
25666
|
+
return void 0;
|
|
25667
|
+
}
|
|
25668
|
+
}
|
|
25669
|
+
var within = (path, dir) => path === dir || path.startsWith(`${dir}${sep2}`);
|
|
25670
|
+
function upTo(dir, stop2) {
|
|
25671
|
+
const dirs = [dir];
|
|
25672
|
+
for (let at = dir; within(at, stop2) && at !== stop2 && dirname4(at) !== at; ) {
|
|
25673
|
+
at = dirname4(at);
|
|
25674
|
+
dirs.push(at);
|
|
25675
|
+
}
|
|
25676
|
+
return dirs;
|
|
25677
|
+
}
|
|
25678
|
+
function namedFiles(sources, folder2, root, home3) {
|
|
25679
|
+
const found = [];
|
|
25680
|
+
const glob = (pattern, cwd) => {
|
|
25681
|
+
try {
|
|
25682
|
+
return globSync(pattern, { cwd }).map((match) => join5(cwd, match));
|
|
25683
|
+
} catch {
|
|
25684
|
+
return [];
|
|
25685
|
+
}
|
|
25686
|
+
};
|
|
25687
|
+
for (const source2 of sources) {
|
|
25688
|
+
if (/^https?:\/\//i.test(source2)) continue;
|
|
25689
|
+
const expanded = source2.startsWith("~/") ? join5(home3, source2.slice(2)) : source2;
|
|
25690
|
+
const matches = isAbsolute2(expanded) ? glob(expanded.slice(dirname4(expanded).length + 1), dirname4(expanded)) : upTo(folder2, within(folder2, root) ? root : folder2).flatMap((dir) => glob(expanded, dir));
|
|
25691
|
+
for (const path of matches.sort()) if (isFile(path) && !found.includes(path)) found.push(path);
|
|
25692
|
+
}
|
|
25693
|
+
return found;
|
|
25694
|
+
}
|
|
25695
|
+
function before(version3, than) {
|
|
25696
|
+
const parts = (each) => each.split(/[.-]/).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
|
|
25697
|
+
const [a, b] = [parts(version3), parts(than)];
|
|
25698
|
+
for (let at = 0; at < 3; at += 1) if ((a[at] ?? 0) !== (b[at] ?? 0)) return (a[at] ?? 0) < (b[at] ?? 0);
|
|
25699
|
+
return false;
|
|
25700
|
+
}
|
|
25701
|
+
var READS_NAMED = "2.0.30";
|
|
25702
|
+
var ContextFiles = class {
|
|
25703
|
+
constructor(places, folders) {
|
|
25704
|
+
this.places = places;
|
|
25705
|
+
this.folders = folders;
|
|
25706
|
+
}
|
|
25707
|
+
places;
|
|
25708
|
+
folders;
|
|
25709
|
+
/** `~/chips-opencode/agents/AGENTS.md`, or the whole path where it isn't in the home. */
|
|
25710
|
+
tilde(path) {
|
|
25711
|
+
const { home: home3 } = this.places;
|
|
25712
|
+
return within(path, home3) ? `~${path.slice(home3.length)}` : path;
|
|
25713
|
+
}
|
|
25714
|
+
/** A shared file's name: where it is, a step at a time (`~ › .config › opencode › AGENTS.md`), with no slash. */
|
|
25715
|
+
crumbs(path) {
|
|
25716
|
+
const { home: home3 } = this.places;
|
|
25717
|
+
const steps = within(path, home3) ? ["~", ...relative(home3, path).split(sep2)] : ["/", ...path.split(sep2)];
|
|
25718
|
+
return steps.filter((step) => step !== "").join(" \u203A ").slice(0, 255);
|
|
25719
|
+
}
|
|
25720
|
+
/**
|
|
25721
|
+
* The harness's own set, for agents whose folders read as `readings` says: every agent's
|
|
25722
|
+
* `AGENTS.md`, those above it, the person's own, and the named files not in an agent's folder.
|
|
25723
|
+
*/
|
|
25724
|
+
sharedSpots(readings) {
|
|
25725
|
+
const { places, folders } = this;
|
|
25726
|
+
const spots = [];
|
|
25727
|
+
const add = (spot) => {
|
|
25728
|
+
if (!spots.some((each) => each.path === spot.path)) spots.push(spot);
|
|
25729
|
+
};
|
|
25730
|
+
const reaches = (dir) => (folder2) => within(folder2, dir) && within(dir, readings.get(folder2)?.stop ?? folder2);
|
|
25731
|
+
if (folders !== void 0 && within(folders, places.home)) {
|
|
25732
|
+
const path = join5(folders, AGENTS_MD);
|
|
25733
|
+
add({
|
|
25734
|
+
path,
|
|
25735
|
+
name: this.crumbs(path),
|
|
25736
|
+
title: "For every OpenCode agent",
|
|
25737
|
+
about: this.tilde(path),
|
|
25738
|
+
editable: true,
|
|
25739
|
+
id: "every-agent",
|
|
25740
|
+
over: reaches(folders)
|
|
25741
|
+
});
|
|
25742
|
+
}
|
|
25743
|
+
const yours = join5(places.config, AGENTS_MD);
|
|
25744
|
+
for (const [folder2, reading] of readings) {
|
|
25745
|
+
for (const dir of upTo(folder2, reading.stop).slice(1)) {
|
|
25746
|
+
const path = join5(dir, AGENTS_MD);
|
|
25747
|
+
if (path === yours || !isFile(path)) continue;
|
|
25748
|
+
add({
|
|
25749
|
+
path,
|
|
25750
|
+
name: this.crumbs(path),
|
|
25751
|
+
title: `From ${this.tilde(dir)}`.slice(0, 80),
|
|
25752
|
+
about: this.tilde(path),
|
|
25753
|
+
editable: false,
|
|
25754
|
+
whyReadOnly: "It sits above the agents' folders.",
|
|
25755
|
+
id: `above-${hashOf(path).slice(0, 12)}`,
|
|
25756
|
+
over: reaches(dir)
|
|
25757
|
+
});
|
|
25758
|
+
}
|
|
25759
|
+
}
|
|
25760
|
+
if (isFile(yours)) {
|
|
25761
|
+
add({
|
|
25762
|
+
path: yours,
|
|
25763
|
+
name: this.crumbs(yours),
|
|
25764
|
+
title: "For all your OpenCode",
|
|
25765
|
+
about: this.tilde(yours),
|
|
25766
|
+
editable: false,
|
|
25767
|
+
whyReadOnly: "Your own OpenCode reads it too.",
|
|
25768
|
+
id: "yours"
|
|
25769
|
+
});
|
|
25770
|
+
}
|
|
25771
|
+
for (const [folder2, reading] of readings) {
|
|
25772
|
+
for (const path of reading.named) {
|
|
25773
|
+
if (within(path, folder2)) continue;
|
|
25774
|
+
add({
|
|
25775
|
+
path,
|
|
25776
|
+
name: this.crumbs(path),
|
|
25777
|
+
title: "Named in OpenCode's config",
|
|
25778
|
+
about: this.tilde(path),
|
|
25779
|
+
editable: false,
|
|
25780
|
+
whyReadOnly: "OpenCode's config names it for every session.",
|
|
25781
|
+
id: `named-${hashOf(path).slice(0, 12)}`,
|
|
25782
|
+
over: (other) => readings.get(other)?.named.includes(path) === true
|
|
25783
|
+
});
|
|
25784
|
+
}
|
|
25785
|
+
}
|
|
25786
|
+
return spots;
|
|
25787
|
+
}
|
|
25788
|
+
/** An agent's own files: its folder's `AGENTS.md`, and the named files in its folder. */
|
|
25789
|
+
ownSpots(folder2, reading) {
|
|
25790
|
+
const named = reading.named.filter((path) => within(path, folder2) && path !== join5(folder2, AGENTS_MD));
|
|
25791
|
+
return [
|
|
25792
|
+
{ path: join5(folder2, AGENTS_MD), name: AGENTS_MD, title: "How it works", editable: true },
|
|
25793
|
+
...named.map((path) => ({
|
|
25794
|
+
path,
|
|
25795
|
+
name: relative(folder2, path).split(sep2).join(" \u203A ").slice(0, 255),
|
|
25796
|
+
title: "Named in OpenCode's config",
|
|
25797
|
+
editable: true
|
|
25798
|
+
}))
|
|
25799
|
+
];
|
|
25800
|
+
}
|
|
25801
|
+
/** The shared files an agent in `folder` reads. */
|
|
25802
|
+
readBy(folder2, reading) {
|
|
25803
|
+
return this.sharedSpots(/* @__PURE__ */ new Map([[folder2, reading]])).filter((spot) => spot.over === void 0 || spot.over(folder2));
|
|
25804
|
+
}
|
|
25805
|
+
/** An agent's copy: its own files and the shared ones it reads, eight at most. */
|
|
25806
|
+
agentCopy(folder2, reading) {
|
|
25807
|
+
const rows = [];
|
|
25808
|
+
const notShown = [];
|
|
25809
|
+
const add = (row, text2) => {
|
|
25810
|
+
const named = { name: row["name"], title: row["title"] };
|
|
25811
|
+
if (text2 !== void 0 && contextBytes(text2) > CONTEXT_FILE_BYTES) notShown.push({ ...named, reason: "too_large" });
|
|
25812
|
+
else if (rows.length >= CONTEXT_FILES_SHOWN) notShown.push({ ...named, reason: "count" });
|
|
25813
|
+
else rows.push(row);
|
|
25814
|
+
};
|
|
25815
|
+
for (const spot of this.ownSpots(folder2, reading)) {
|
|
25816
|
+
const text2 = textOf(spot.path) ?? "";
|
|
25817
|
+
add({ name: spot.name, title: spot.title, role: "instructions", text: text2, hash: hashOf(text2), editable: true }, text2);
|
|
25818
|
+
}
|
|
25819
|
+
for (const spot of this.readBy(folder2, reading)) {
|
|
25820
|
+
add({
|
|
25821
|
+
name: spot.name,
|
|
25822
|
+
title: spot.title,
|
|
25823
|
+
role: "instructions",
|
|
25824
|
+
...spot.about === void 0 ? {} : { about: spot.about },
|
|
25825
|
+
shared: spot.id,
|
|
25826
|
+
replaceable: false,
|
|
25827
|
+
editable: false
|
|
25828
|
+
});
|
|
25829
|
+
}
|
|
25830
|
+
return { files: rows, ...notShown.length === 0 ? {} : { not_shown: notShown } };
|
|
25831
|
+
}
|
|
25832
|
+
/** The harness's own set, each file with how many of its agents (by their folders' readings) read it. */
|
|
25833
|
+
harnessCopy(readings, agentFolders) {
|
|
25834
|
+
const shared = [];
|
|
25835
|
+
const notShown = [];
|
|
25836
|
+
for (const spot of this.sharedSpots(readings)) {
|
|
25837
|
+
const text2 = textOf(spot.path) ?? "";
|
|
25838
|
+
const named = { name: spot.name, title: spot.title };
|
|
25839
|
+
if (contextBytes(text2) > CONTEXT_FILE_BYTES) {
|
|
25840
|
+
notShown.push({ ...named, reason: "too_large" });
|
|
25841
|
+
continue;
|
|
25842
|
+
}
|
|
25843
|
+
if (shared.length >= CONTEXT_FILES_SHOWN) {
|
|
25844
|
+
notShown.push({ ...named, reason: "count" });
|
|
25845
|
+
continue;
|
|
25846
|
+
}
|
|
25847
|
+
const over = spot.over;
|
|
25848
|
+
shared.push({
|
|
25849
|
+
id: spot.id,
|
|
25850
|
+
name: spot.name,
|
|
25851
|
+
title: spot.title,
|
|
25852
|
+
role: "instructions",
|
|
25853
|
+
...spot.about === void 0 ? {} : { about: spot.about },
|
|
25854
|
+
text: text2,
|
|
25855
|
+
hash: hashOf(text2),
|
|
25856
|
+
editable: spot.editable,
|
|
25857
|
+
...spot.whyReadOnly === void 0 ? {} : { why_read_only: spot.whyReadOnly },
|
|
25858
|
+
used_by: over === void 0 ? agentFolders.length : agentFolders.filter(over).length
|
|
25859
|
+
});
|
|
25860
|
+
}
|
|
25861
|
+
return { shared, ...notShown.length === 0 ? {} : { not_shown: notShown } };
|
|
25862
|
+
}
|
|
25863
|
+
/**
|
|
25864
|
+
* A person's edit of one of an agent's files, saved where OpenCode reads it, only if it is still
|
|
25865
|
+
* the version they changed. Refused, with the copy as it is, for a file that isn't the agent's own
|
|
25866
|
+
* to change, and as `changed` for one changed since.
|
|
25867
|
+
*/
|
|
25868
|
+
async saveAgent(folder2, reading, edit) {
|
|
25869
|
+
const now = () => this.agentCopy(folder2, reading);
|
|
25870
|
+
const spot = this.ownSpots(folder2, reading).find((each) => each.name === edit.name);
|
|
25871
|
+
if (spot === void 0) {
|
|
25872
|
+
const shared = this.readBy(folder2, reading).some((each) => each.name === edit.name);
|
|
25873
|
+
throw new Refuse(
|
|
25874
|
+
shared ? "Every agent reads this one. Change it on OpenCode's page." : `There is no file called ${edit.name} here.`,
|
|
25875
|
+
now(),
|
|
25876
|
+
"failed"
|
|
25877
|
+
);
|
|
25878
|
+
}
|
|
25879
|
+
if (edit.base_hash === null) throw new Refuse("This file is the agent's own already.", now(), "failed");
|
|
25880
|
+
await this.write(spot, edit, now);
|
|
25881
|
+
}
|
|
25882
|
+
/** A person's edit of one of the harness's own files, read by its agents beside their own. */
|
|
25883
|
+
async saveShared(readings, agentFolders, edit) {
|
|
25884
|
+
const now = () => this.harnessCopy(readings, agentFolders);
|
|
25885
|
+
const spot = this.sharedSpots(readings).find((each) => each.name === edit.name);
|
|
25886
|
+
if (spot === void 0) throw new Refuse(`There is no file called ${edit.name} here.`, now(), "failed");
|
|
25887
|
+
if (!spot.editable) throw new Refuse(spot.whyReadOnly ?? "It can't be changed from Chips.", now(), "failed");
|
|
25888
|
+
if (edit.base_hash === null) {
|
|
25889
|
+
throw new Refuse("Every agent reads this file beside its own, so none can have its own in its place.", now(), "failed");
|
|
25890
|
+
}
|
|
25891
|
+
await this.write(spot, edit, now);
|
|
25892
|
+
}
|
|
25893
|
+
async write(spot, edit, now) {
|
|
25894
|
+
if (hashOf(textOf(spot.path) ?? "") !== edit.base_hash) throw new Refuse("", now(), "changed");
|
|
25895
|
+
try {
|
|
25896
|
+
await mkdir2(dirname4(spot.path), { recursive: true });
|
|
25897
|
+
await writeFile2(spot.path, edit.text, "utf8");
|
|
25898
|
+
} catch (error62) {
|
|
25899
|
+
throw new Refuse(`It couldn't be saved: ${error62 instanceof Error ? error62.message : String(error62)}`.slice(0, 300), now(), "failed");
|
|
25900
|
+
}
|
|
25901
|
+
}
|
|
25902
|
+
};
|
|
25471
25903
|
|
|
25472
25904
|
// src/opencode.ts
|
|
25473
25905
|
var OpenCodeError = class extends Error {
|
|
@@ -25507,12 +25939,47 @@ var OpenCodeClient = class {
|
|
|
25507
25939
|
return false;
|
|
25508
25940
|
}
|
|
25509
25941
|
}
|
|
25942
|
+
/** Which OpenCode answers: `GET /api/info`'s `version`, `2.0.25`. */
|
|
25943
|
+
async version() {
|
|
25944
|
+
const info = await this.call("GET", "/api/info");
|
|
25945
|
+
return typeof info["version"] === "string" ? info["version"] : "";
|
|
25946
|
+
}
|
|
25947
|
+
/** The root of the project a folder is in, as OpenCode has it: the folder itself, outside any repository. */
|
|
25948
|
+
async projectRoot(directory) {
|
|
25949
|
+
const read = await this.call("GET", `/api/location${whereIs(directory)}`);
|
|
25950
|
+
const project = read["project"] ?? {};
|
|
25951
|
+
return typeof project["directory"] === "string" ? project["directory"] : directory;
|
|
25952
|
+
}
|
|
25953
|
+
/**
|
|
25954
|
+
* The instruction files the config documents for a folder name (`instructions`), each document's
|
|
25955
|
+
* in turn, each named once: a path, a glob or a URL, as written.
|
|
25956
|
+
*/
|
|
25957
|
+
async instructions(directory) {
|
|
25958
|
+
const answer = await this.fetcher(`${this.root}/api/config${whereIs(directory)}`, { headers: { authorization: this.auth } });
|
|
25959
|
+
if (!answer.ok) throw await failureOf(answer);
|
|
25960
|
+
const entries = await answer.json();
|
|
25961
|
+
const named = (Array.isArray(entries) ? entries : []).filter((entry) => entry["type"] === "document").flatMap((entry) => (entry["info"] ?? {})["instructions"]).filter((each) => typeof each === "string");
|
|
25962
|
+
return [...new Set(named)];
|
|
25963
|
+
}
|
|
25510
25964
|
async agents() {
|
|
25511
25965
|
return await this.catalogue("agent");
|
|
25512
25966
|
}
|
|
25513
25967
|
async models() {
|
|
25514
25968
|
return await this.catalogue("model");
|
|
25515
25969
|
}
|
|
25970
|
+
/** The model a session with none of its own runs on, when the server has one. */
|
|
25971
|
+
async defaultModel() {
|
|
25972
|
+
const read = await this.call("GET", `/api/model/default${this.where()}`);
|
|
25973
|
+
const data = read["data"];
|
|
25974
|
+
return typeof data === "object" && data !== null ? data : void 0;
|
|
25975
|
+
}
|
|
25976
|
+
/**
|
|
25977
|
+
* The model a session runs on from its next model call. OpenCode takes any model and level
|
|
25978
|
+
* here, one it can't run among them, so the bridge asks only for those of its catalogue.
|
|
25979
|
+
*/
|
|
25980
|
+
async setModel(sessionId, model) {
|
|
25981
|
+
await this.call("POST", `/api/session/${encodeURIComponent(sessionId)}/model`, { model });
|
|
25982
|
+
}
|
|
25516
25983
|
/**
|
|
25517
25984
|
* A project's catalogue. OpenCode 2.0 answers the first ask for a project it hasn't loaded with
|
|
25518
25985
|
* an empty list, and the next with the whole one: a server always has its `build` agent, so an
|
|
@@ -25577,6 +26044,13 @@ var OpenCodeClient = class {
|
|
|
25577
26044
|
if (typeof id2 !== "string") throw new OpenCodeError("OpenCode took the words and named no inbox item", 502);
|
|
25578
26045
|
return id2;
|
|
25579
26046
|
}
|
|
26047
|
+
/**
|
|
26048
|
+
* Compact a session: its inbox takes the ask, which starts at once when it is idle and at its next
|
|
26049
|
+
* step when it is working. `session.compaction.started` and `.ended` or `.failed` say how it goes.
|
|
26050
|
+
*/
|
|
26051
|
+
async compact(sessionId) {
|
|
26052
|
+
await this.call("POST", `/api/session/${encodeURIComponent(sessionId)}/compact`, {});
|
|
26053
|
+
}
|
|
25580
26054
|
/** Words the session hasn't taken yet, taken back. */
|
|
25581
26055
|
async withdraw(sessionId, item) {
|
|
25582
26056
|
await this.call("DELETE", `/api/session/${encodeURIComponent(sessionId)}/inbox/${encodeURIComponent(item)}`);
|
|
@@ -25626,6 +26100,22 @@ var OpenCodeClient = class {
|
|
|
25626
26100
|
const content = Array.isArray(newest?.["content"]) ? newest["content"] : [];
|
|
25627
26101
|
return content.filter((part) => part["type"] === "text" && typeof part["text"] === "string").map((part) => String(part["text"])).join("\n\n");
|
|
25628
26102
|
}
|
|
26103
|
+
/** The tokens and model of a session's newest model call, and when: nothing for a session with none. */
|
|
26104
|
+
async lastCall(sessionId) {
|
|
26105
|
+
const read = await this.call(
|
|
26106
|
+
"GET",
|
|
26107
|
+
`/api/session/${encodeURIComponent(sessionId)}/message?type=assistant&order=desc&limit=1`
|
|
26108
|
+
);
|
|
26109
|
+
const newest = (Array.isArray(read["data"]) ? read["data"] : [])[0];
|
|
26110
|
+
const tokens2 = newest?.["tokens"];
|
|
26111
|
+
if (typeof tokens2 !== "object" || tokens2 === null) return void 0;
|
|
26112
|
+
const time3 = newest?.["time"] ?? {};
|
|
26113
|
+
return {
|
|
26114
|
+
tokens: tokens2,
|
|
26115
|
+
...typeof newest?.["model"] === "object" && newest["model"] !== null ? { model: newest["model"] } : {},
|
|
26116
|
+
at: typeof time3["created"] === "number" ? time3["created"] : 0
|
|
26117
|
+
};
|
|
26118
|
+
}
|
|
25629
26119
|
/** Every event the server sends, each handed to `hear` as it comes, until the stream ends. */
|
|
25630
26120
|
async events(hear, signal) {
|
|
25631
26121
|
const answer = await this.fetcher(`${this.root}/api/event`, {
|
|
@@ -25649,7 +26139,7 @@ var OpenCodeClient = class {
|
|
|
25649
26139
|
/** `?location[directory]=…` for the catalogues, which are per project. */
|
|
25650
26140
|
where() {
|
|
25651
26141
|
const directory = this.address.directory;
|
|
25652
|
-
return directory === void 0 ? "" :
|
|
26142
|
+
return directory === void 0 ? "" : whereIs(directory);
|
|
25653
26143
|
}
|
|
25654
26144
|
async call(method, path, body) {
|
|
25655
26145
|
const answer = await this.fetcher(`${this.root}${path}`, {
|
|
@@ -25665,6 +26155,9 @@ var OpenCodeClient = class {
|
|
|
25665
26155
|
return text2 === "" ? {} : JSON.parse(text2);
|
|
25666
26156
|
}
|
|
25667
26157
|
};
|
|
26158
|
+
function whereIs(directory) {
|
|
26159
|
+
return `?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
|
|
26160
|
+
}
|
|
25668
26161
|
function eventOf(block) {
|
|
25669
26162
|
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
|
|
25670
26163
|
if (data === "") return void 0;
|
|
@@ -25696,15 +26189,131 @@ async function failureOf(answer) {
|
|
|
25696
26189
|
return new OpenCodeError(message, answer.status, tag, kind);
|
|
25697
26190
|
}
|
|
25698
26191
|
|
|
26192
|
+
// src/models.ts
|
|
26193
|
+
var MODEL_CHOICES_MAX2 = 40;
|
|
26194
|
+
var EFFORT_LEVELS_MAX2 = 8;
|
|
26195
|
+
var LEVEL_WORDS = {
|
|
26196
|
+
none: "None",
|
|
26197
|
+
minimal: "Minimal",
|
|
26198
|
+
low: "Low",
|
|
26199
|
+
medium: "Medium",
|
|
26200
|
+
high: "High",
|
|
26201
|
+
xhigh: "Extra high",
|
|
26202
|
+
max: "Max",
|
|
26203
|
+
thinking: "Thinking"
|
|
26204
|
+
};
|
|
26205
|
+
function writtenOf(model) {
|
|
26206
|
+
return `${model.providerID}/${model.id}`;
|
|
26207
|
+
}
|
|
26208
|
+
function refOf(pick2) {
|
|
26209
|
+
return { ...modelRefOf(pick2.model), ...pick2.effort === void 0 ? {} : { variant: pick2.effort } };
|
|
26210
|
+
}
|
|
26211
|
+
function levelOf(model) {
|
|
26212
|
+
return model?.variant === void 0 || model.variant === "default" ? void 0 : model.variant;
|
|
26213
|
+
}
|
|
26214
|
+
function sameModel(a, b) {
|
|
26215
|
+
return a?.providerID === b?.providerID && a?.id === b?.id && levelOf(a) === levelOf(b);
|
|
26216
|
+
}
|
|
26217
|
+
function levelWords(id2) {
|
|
26218
|
+
return (LEVEL_WORDS[id2] ?? `${id2.charAt(0).toUpperCase()}${id2.slice(1)}`).slice(0, 40);
|
|
26219
|
+
}
|
|
26220
|
+
function runnable(catalogue) {
|
|
26221
|
+
return catalogue.filter((model) => model.enabled !== false && model.status !== "deprecated");
|
|
26222
|
+
}
|
|
26223
|
+
function levelsOf(model) {
|
|
26224
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26225
|
+
const each = (model?.variants ?? []).filter((variant) => !seen.has(variant.id) && seen.add(variant.id));
|
|
26226
|
+
return each.slice(-EFFORT_LEVELS_MAX2).map((variant) => ({ id: variant.id, label: levelWords(variant.id) }));
|
|
26227
|
+
}
|
|
26228
|
+
var fitting = (choice, effort) => choice?.efforts.some((level) => level.id === effort) === true ? effort : void 0;
|
|
26229
|
+
function modelCopy(input2) {
|
|
26230
|
+
const { pick: pick2, fallback } = input2;
|
|
26231
|
+
const runsOn = pick2 === void 0 ? fallback?.model : refOf(pick2);
|
|
26232
|
+
if (runsOn === void 0) return void 0;
|
|
26233
|
+
const offered2 = runnable(input2.catalogue);
|
|
26234
|
+
const names = /* @__PURE__ */ new Map();
|
|
26235
|
+
for (const model of offered2) names.set(model.name, (names.get(model.name) ?? 0) + 1);
|
|
26236
|
+
const choices = offered2.map((model) => ({
|
|
26237
|
+
id: writtenOf(model),
|
|
26238
|
+
label: ((names.get(model.name) ?? 0) > 1 ? `${model.name} \xB7 ${model.providerID}` : model.name || model.id).slice(0, 80),
|
|
26239
|
+
efforts: levelsOf(model)
|
|
26240
|
+
}));
|
|
26241
|
+
const named = (ref) => {
|
|
26242
|
+
const id2 = writtenOf(ref);
|
|
26243
|
+
const known = choices.find((choice2) => choice2.id === id2);
|
|
26244
|
+
if (known !== void 0) return known;
|
|
26245
|
+
const level = levelOf(ref);
|
|
26246
|
+
const choice = { id: id2, label: ref.id.slice(0, 80), efforts: level === void 0 ? [] : [{ id: level, label: levelWords(level) }] };
|
|
26247
|
+
choices.push(choice);
|
|
26248
|
+
return choice;
|
|
26249
|
+
};
|
|
26250
|
+
const now = named(runsOn);
|
|
26251
|
+
const otherwise = fallback === void 0 ? void 0 : named(fallback.model);
|
|
26252
|
+
const effort = fitting(now, levelOf(runsOn));
|
|
26253
|
+
const fallbackEffort = fitting(otherwise, levelOf(fallback?.model));
|
|
26254
|
+
const keep = /* @__PURE__ */ new Set([now.id, ...otherwise === void 0 ? [] : [otherwise.id]]);
|
|
26255
|
+
let others = MODEL_CHOICES_MAX2 - keep.size;
|
|
26256
|
+
return {
|
|
26257
|
+
model: now.id,
|
|
26258
|
+
...effort === void 0 ? {} : { effort },
|
|
26259
|
+
own: pick2 !== void 0,
|
|
26260
|
+
...fallback === void 0 || otherwise === void 0 ? {} : {
|
|
26261
|
+
default: {
|
|
26262
|
+
label: fallback.label.slice(0, 80),
|
|
26263
|
+
model: otherwise.id,
|
|
26264
|
+
...fallbackEffort === void 0 ? {} : { effort: fallbackEffort }
|
|
26265
|
+
}
|
|
26266
|
+
},
|
|
26267
|
+
choices: choices.filter((choice) => keep.has(choice.id) || others-- > 0),
|
|
26268
|
+
editable: true
|
|
26269
|
+
};
|
|
26270
|
+
}
|
|
26271
|
+
function pickOf(change, catalogue) {
|
|
26272
|
+
if (change.model === null) return null;
|
|
26273
|
+
const model = runnable(catalogue).find((each) => writtenOf(each) === change.model);
|
|
26274
|
+
if (model === void 0) throw new Error(`OpenCode doesn't run ${change.model} here.`);
|
|
26275
|
+
if (change.effort === null) return { model: change.model };
|
|
26276
|
+
if (!(model.variants ?? []).some((variant) => variant.id === change.effort)) {
|
|
26277
|
+
throw new Error(`${model.name || model.id} has no ${levelWords(change.effort)} level.`);
|
|
26278
|
+
}
|
|
26279
|
+
return { model: change.model, effort: change.effort };
|
|
26280
|
+
}
|
|
26281
|
+
|
|
26282
|
+
// src/windows.ts
|
|
26283
|
+
var OWN_LIMIT = "OpenCode compacts it itself when it is nearly full, as its config says.";
|
|
26284
|
+
function usedOf(tokens2) {
|
|
26285
|
+
return tokens2.input + tokens2.cache.read + tokens2.cache.write + tokens2.output + tokens2.reasoning;
|
|
26286
|
+
}
|
|
26287
|
+
function compactedTo(data) {
|
|
26288
|
+
const tokens2 = data["tokens"];
|
|
26289
|
+
const summary = typeof tokens2?.output === "number" ? tokens2.output : 0;
|
|
26290
|
+
const recent = typeof data["recent"] === "string" ? data["recent"] : "";
|
|
26291
|
+
return Math.round(summary + recent.length / 4);
|
|
26292
|
+
}
|
|
26293
|
+
function windowCopy(kept, compacting) {
|
|
26294
|
+
return {
|
|
26295
|
+
used: Math.max(0, Math.round(kept.used)),
|
|
26296
|
+
window: Math.max(1, Math.round(kept.window)),
|
|
26297
|
+
...kept.model === void 0 ? {} : { model: kept.model.slice(0, 120) },
|
|
26298
|
+
as_of: kept.asOf,
|
|
26299
|
+
...compacting === void 0 ? {} : { compacting },
|
|
26300
|
+
...kept.last === void 0 ? {} : { last: kept.last },
|
|
26301
|
+
...kept.longTurn === void 0 ? {} : { long_turn: { ...kept.longTurn, folds: Math.min(kept.longTurn.folds, LONG_TURN_FOLDS_MAX) } },
|
|
26302
|
+
compacts: true,
|
|
26303
|
+
editable: false,
|
|
26304
|
+
why_read_only: OWN_LIMIT
|
|
26305
|
+
};
|
|
26306
|
+
}
|
|
26307
|
+
|
|
25699
26308
|
// src/bridge.ts
|
|
25700
|
-
var WANTS = ["agents", "new_agents", "forks"];
|
|
26309
|
+
var WANTS = ["agents", "new_spaces", "new_agents", "forks", "models", "context_files", "context_windows"];
|
|
25701
26310
|
function wantsFor(ownServer2) {
|
|
25702
26311
|
return ownServer2 ? [...WANTS, "restarts"] : WANTS;
|
|
25703
26312
|
}
|
|
25704
26313
|
function freeFolder(root, name, taken) {
|
|
25705
26314
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
25706
26315
|
for (let count = 1; ; count += 1) {
|
|
25707
|
-
const folder2 =
|
|
26316
|
+
const folder2 = join6(root, count === 1 ? slug : `${slug}-${count}`);
|
|
25708
26317
|
if (!taken.includes(folder2)) return folder2;
|
|
25709
26318
|
}
|
|
25710
26319
|
}
|
|
@@ -25735,12 +26344,17 @@ var Bridge = class {
|
|
|
25735
26344
|
constructor(options) {
|
|
25736
26345
|
this.options = options;
|
|
25737
26346
|
this.log = options.log ?? (() => void 0);
|
|
26347
|
+
this.places = options.places ?? placesOf(process.env, homedir4());
|
|
26348
|
+
const known = options.folders !== void 0 || options.directory !== void 0;
|
|
26349
|
+
this.files = known ? new ContextFiles(this.places, options.folders) : void 0;
|
|
25738
26350
|
}
|
|
25739
26351
|
options;
|
|
25740
26352
|
agents = [];
|
|
25741
26353
|
rooms = /* @__PURE__ */ new Map();
|
|
25742
26354
|
/** The turn going in each OpenCode session. */
|
|
25743
26355
|
turns = /* @__PURE__ */ new Map();
|
|
26356
|
+
/** The room's rules each session holds (Chips ADR 0250): said once, and again after it compacts. */
|
|
26357
|
+
rules = new SessionRules();
|
|
25744
26358
|
/**
|
|
25745
26359
|
* What a session said while it was being given a turn's words, before the turn was known: OpenCode
|
|
25746
26360
|
* may say it took them before it answers that it has them. Heard by the turn once it is.
|
|
@@ -25770,6 +26384,15 @@ var Bridge = class {
|
|
|
25770
26384
|
restarting;
|
|
25771
26385
|
/** Why OpenCode's server isn't there, where the bridge knows more than that it doesn't answer. */
|
|
25772
26386
|
why;
|
|
26387
|
+
/** The files OpenCode builds each agent from, where the bridge knows each agent's folder. */
|
|
26388
|
+
files;
|
|
26389
|
+
places;
|
|
26390
|
+
/** Each folder's project root, as OpenCode said it. */
|
|
26391
|
+
roots = /* @__PURE__ */ new Map();
|
|
26392
|
+
/** Whether the server reads the files its config's `instructions` names, asked once a start or a restart. */
|
|
26393
|
+
readsNamed;
|
|
26394
|
+
/** Agents compacting now, by whose sessions they are: waiting for their session's next step, or under way, and the ask from Chips. */
|
|
26395
|
+
compacting = /* @__PURE__ */ new Map();
|
|
25773
26396
|
/** Register the agents, start hearing OpenCode and what is said in Chips, and say how it is. */
|
|
25774
26397
|
async start() {
|
|
25775
26398
|
const { chips: chips2 } = this.options;
|
|
@@ -25789,9 +26412,17 @@ var Bridge = class {
|
|
|
25789
26412
|
chips2.on("agent.fork", (fork) => {
|
|
25790
26413
|
this.copy(fork).catch((error62) => this.log(`a copy went wrong: ${messageOf(error62)}`));
|
|
25791
26414
|
});
|
|
26415
|
+
chips2.onSpaceAsked((asked) => {
|
|
26416
|
+
this.claim(asked).catch((error62) => this.log(`a space from the + went wrong: ${messageOf(error62)}`));
|
|
26417
|
+
});
|
|
25792
26418
|
chips2.on("feedback", (feedback2) => {
|
|
25793
26419
|
this.notRight(feedback2).catch((error62) => this.log(`a second look went wrong: ${messageOf(error62)}`));
|
|
25794
26420
|
});
|
|
26421
|
+
for (const agent of await chips2.agents.list().catch(() => [])) {
|
|
26422
|
+
if (agent.state !== "archived") await this.sendModel(agent.agentId);
|
|
26423
|
+
}
|
|
26424
|
+
await this.sendContexts();
|
|
26425
|
+
await this.sendWindows();
|
|
25795
26426
|
await this.sayHowItIs();
|
|
25796
26427
|
this.health = setInterval(() => void this.sayHowItIs(), this.options.healthEveryMs ?? 3e4);
|
|
25797
26428
|
this.health.unref();
|
|
@@ -25823,6 +26454,7 @@ var Bridge = class {
|
|
|
25823
26454
|
try {
|
|
25824
26455
|
await again2();
|
|
25825
26456
|
this.why = void 0;
|
|
26457
|
+
this.readsNamed = void 0;
|
|
25826
26458
|
this.wake?.();
|
|
25827
26459
|
} catch (error62) {
|
|
25828
26460
|
const why = messageOf(error62).replace(/\s+/g, " ").trim();
|
|
@@ -25844,16 +26476,16 @@ var Bridge = class {
|
|
|
25844
26476
|
*/
|
|
25845
26477
|
async seatMain(config3, known) {
|
|
25846
26478
|
const { chips: chips2 } = this.options;
|
|
25847
|
-
const
|
|
26479
|
+
const before2 = known.find((agent) => agent.lead);
|
|
25848
26480
|
const made = known.some((agent) => agent.key === chips2.harnessId);
|
|
25849
|
-
if (!made &&
|
|
25850
|
-
await this.goHome(
|
|
25851
|
-
return
|
|
26481
|
+
if (!made && before2 !== void 0) {
|
|
26482
|
+
await this.goHome(before2, known);
|
|
26483
|
+
return before2;
|
|
25852
26484
|
}
|
|
25853
26485
|
const main2 = await chips2.agents.register({ key: chips2.harnessId, name: config3.name, lead: true });
|
|
25854
26486
|
await this.goHome(main2, known);
|
|
25855
|
-
if (
|
|
25856
|
-
await chips2.agents.register({ key:
|
|
26487
|
+
if (before2 !== void 0 && before2.agentId !== main2.agentId) {
|
|
26488
|
+
await chips2.agents.register({ key: before2.key, name: before2.name, state: "archived" }).catch((error62) => this.log(`${before2.name} stays: ${String(error62)}`));
|
|
25857
26489
|
}
|
|
25858
26490
|
return main2;
|
|
25859
26491
|
}
|
|
@@ -25915,6 +26547,47 @@ var Bridge = class {
|
|
|
25915
26547
|
});
|
|
25916
26548
|
this.ours.add(agent.agentId);
|
|
25917
26549
|
this.log(`${agent.name} made${agent.attached === false ? `, not in the room: ${agent.why ?? "too late"}` : ""}`);
|
|
26550
|
+
await this.sendModel(agent.agentId);
|
|
26551
|
+
await this.sendContexts();
|
|
26552
|
+
}
|
|
26553
|
+
/**
|
|
26554
|
+
* A space a person asked for from Chips's + (Chips ADR 0241), or beside another harness: claimed
|
|
26555
|
+
* under a key of its own, named from its first words, and Chips makes the new agent the key is
|
|
26556
|
+
* for and puts it in the space. That agent answers the first words, which are the ask's and are
|
|
26557
|
+
* not heard again as a message, as the first OpenCode agent, in a session and a folder of its
|
|
26558
|
+
* own. A claim heard again finds the space claimed already, and the agent the same.
|
|
26559
|
+
*/
|
|
26560
|
+
async claim(asked) {
|
|
26561
|
+
const { chips: chips2 } = this.options;
|
|
26562
|
+
const { spaceId, firstMessage } = asked;
|
|
26563
|
+
const key = `opencode:space:${spaceId}`;
|
|
26564
|
+
await chips2.claimSpace(spaceId, key, titleFrom(firstMessage.text));
|
|
26565
|
+
const agent = (await chips2.agents.list()).find((each) => each.key === key);
|
|
26566
|
+
if (agent === void 0) {
|
|
26567
|
+
this.log(`claimed ${spaceId}, and Chips put no agent there`);
|
|
26568
|
+
return;
|
|
26569
|
+
}
|
|
26570
|
+
this.ours.add(agent.agentId);
|
|
26571
|
+
this.log(`${agent.name} claimed ${spaceId}`);
|
|
26572
|
+
await this.sendModel(agent.agentId);
|
|
26573
|
+
await this.sendContexts();
|
|
26574
|
+
const context = await chips2.roomContext(spaceId, { limit: 20 }).catch(() => void 0);
|
|
26575
|
+
const said = context?.lines.find((line) => line.message_id === firstMessage.messageId)?.from;
|
|
26576
|
+
const message = {
|
|
26577
|
+
type: "message.new",
|
|
26578
|
+
space_id: spaceId,
|
|
26579
|
+
message_id: firstMessage.messageId,
|
|
26580
|
+
text: firstMessage.text,
|
|
26581
|
+
from: {
|
|
26582
|
+
participant_id: asked.askedBy,
|
|
26583
|
+
kind: "person",
|
|
26584
|
+
display_name: said?.name ?? "Someone",
|
|
26585
|
+
role: said?.role ?? "member",
|
|
26586
|
+
...said?.workspace_role === void 0 ? {} : { workspace_role: said.workspace_role }
|
|
26587
|
+
},
|
|
26588
|
+
for: [agent.agentId]
|
|
26589
|
+
};
|
|
26590
|
+
await this.hear(message);
|
|
25918
26591
|
}
|
|
25919
26592
|
/** A person's Fork, made once however often it is heard at a time. */
|
|
25920
26593
|
copy(fork) {
|
|
@@ -25976,6 +26649,8 @@ var Bridge = class {
|
|
|
25976
26649
|
this.ours.add(copy.agentId);
|
|
25977
26650
|
if (kept.session !== void 0) state2.sessions[`${copy.agentId}|${spaceId}`] = kept.session;
|
|
25978
26651
|
if (kept.folder !== void 0) state2.folders = { ...state2.folders, [copy.agentId]: kept.folder };
|
|
26652
|
+
const pick2 = state2.models?.[who];
|
|
26653
|
+
if (pick2 !== void 0) state2.models = { ...state2.models, [copy.agentId]: pick2 };
|
|
25979
26654
|
saveState(state2);
|
|
25980
26655
|
if (copied !== void 0) {
|
|
25981
26656
|
await opencode2.update(copied.id, {
|
|
@@ -25986,6 +26661,7 @@ var Bridge = class {
|
|
|
25986
26661
|
this.log(
|
|
25987
26662
|
`${copy.name}, a copy of ${name}, made${kept.session === void 0 ? " new" : ` from ${kept.session}`}${copy.attached === false && copy.why !== void 0 ? `; not in the room: ${copy.why}` : ""}`
|
|
25988
26663
|
);
|
|
26664
|
+
void this.sendModel(copy.agentId).then(() => this.sendContexts());
|
|
25989
26665
|
}
|
|
25990
26666
|
/** The session of an agent's to copy: its room's, or where it is in none, the last it had. */
|
|
25991
26667
|
sessionToCopy(who, room) {
|
|
@@ -25993,6 +26669,305 @@ var Bridge = class {
|
|
|
25993
26669
|
if (room !== void 0 && room !== null) return sessions[`${who}|${room}`];
|
|
25994
26670
|
return Object.entries(sessions).findLast(([id2]) => id2.startsWith(`${who}|`))?.[1];
|
|
25995
26671
|
}
|
|
26672
|
+
// ------------------------------------------------------------------ what a person changes from an agent's page
|
|
26673
|
+
/**
|
|
26674
|
+
* Which OpenCode agent a Chips agent of the harness's runs as, and whose its sessions and its
|
|
26675
|
+
* pick are, as `roomOf` says: the key of one configured here, else its Chips id. Nothing for an
|
|
26676
|
+
* agent not the harness's.
|
|
26677
|
+
*/
|
|
26678
|
+
async whoseIs(agentId2) {
|
|
26679
|
+
const agent = await this.runnerOf(agentId2);
|
|
26680
|
+
if (agent === void 0 || agentId2 === this.options.chips.harnessId) return void 0;
|
|
26681
|
+
return { agent, who: agentId2 === agent.agentId ? agent.key : agentId2 };
|
|
26682
|
+
}
|
|
26683
|
+
/**
|
|
26684
|
+
* What an agent runs on with no pick of its own: its model in the bridge's config, else its
|
|
26685
|
+
* OpenCode agent's, else the server's default.
|
|
26686
|
+
*/
|
|
26687
|
+
async fallbackOf(agent) {
|
|
26688
|
+
if (agent.model !== void 0) return { model: agent.model, label: "As in config.json" };
|
|
26689
|
+
const { opencode: opencode2 } = this.options;
|
|
26690
|
+
const own2 = (await opencode2.agents().catch(() => [])).find((each) => each.id === agent.config.agent)?.model;
|
|
26691
|
+
if (own2 !== void 0) return { model: own2, label: "OpenCode's default" };
|
|
26692
|
+
const model = await opencode2.defaultModel();
|
|
26693
|
+
return model === void 0 ? void 0 : { model: { providerID: model.providerID, id: model.id }, label: "OpenCode's default" };
|
|
26694
|
+
}
|
|
26695
|
+
/** An agent's model, as OpenCode and the bridge have it now: nothing for an agent not the harness's. */
|
|
26696
|
+
async modelCopyOf(agentId2) {
|
|
26697
|
+
const whose = await this.whoseIs(agentId2);
|
|
26698
|
+
if (whose === void 0) return void 0;
|
|
26699
|
+
const [catalogue, fallback] = await Promise.all([this.options.opencode.models(), this.fallbackOf(whose.agent)]);
|
|
26700
|
+
return modelCopy({ catalogue, pick: this.options.state.models?.[whose.who], fallback });
|
|
26701
|
+
}
|
|
26702
|
+
/** An agent's model sent, where it changed since it last was (Chips takes the same copy once). */
|
|
26703
|
+
async sendModel(agentId2) {
|
|
26704
|
+
try {
|
|
26705
|
+
const copy = await this.modelCopyOf(agentId2);
|
|
26706
|
+
if (copy !== void 0) await this.options.chips.settings.model(agentId2, copy);
|
|
26707
|
+
} catch (error62) {
|
|
26708
|
+
this.log(`the model of ${agentId2} not sent: ${messageOf(error62)}`);
|
|
26709
|
+
}
|
|
26710
|
+
}
|
|
26711
|
+
/**
|
|
26712
|
+
* A person's pick of an agent's model and level from its page (Chips ADR 0241): kept with the
|
|
26713
|
+
* bridge's state, and given to each of its sessions before its next message there. Only a model
|
|
26714
|
+
* OpenCode runs, at one of its levels; `null` is its default again. Answered with its copy.
|
|
26715
|
+
*/
|
|
26716
|
+
async modelChanged(change) {
|
|
26717
|
+
const whose = await this.whoseIs(change.agent_id);
|
|
26718
|
+
if (whose === void 0) throw new Refuse("This harness has no such agent.");
|
|
26719
|
+
const { opencode: opencode2, state: state2, saveState } = this.options;
|
|
26720
|
+
let pick2;
|
|
26721
|
+
try {
|
|
26722
|
+
pick2 = pickOf(change, await opencode2.models());
|
|
26723
|
+
} catch (error62) {
|
|
26724
|
+
throw new Refuse(messageOf(error62), await this.modelCopyOf(change.agent_id).catch(() => void 0));
|
|
26725
|
+
}
|
|
26726
|
+
const models = { ...state2.models };
|
|
26727
|
+
if (pick2 === null) delete models[whose.who];
|
|
26728
|
+
else models[whose.who] = pick2;
|
|
26729
|
+
state2.models = models;
|
|
26730
|
+
saveState(state2);
|
|
26731
|
+
const name = await this.nameOf(whose);
|
|
26732
|
+
this.log(`${name} runs on ${pick2 === null ? "its default" : `${pick2.model}${pick2.effort === void 0 ? "" : ` at ${pick2.effort}`}`}, from Chips`);
|
|
26733
|
+
const copy = await this.modelCopyOf(change.agent_id);
|
|
26734
|
+
if (copy === void 0) throw new Refuse("OpenCode names no model for it.");
|
|
26735
|
+
return copy;
|
|
26736
|
+
}
|
|
26737
|
+
/**
|
|
26738
|
+
* The session a message goes to, put on the model its agent runs on, where it isn't on it
|
|
26739
|
+
* already (Chips ADR 0241): its pick, else its model in the bridge's config. A session that was
|
|
26740
|
+
* put on another before, with neither now, goes back to its OpenCode agent's or the server's.
|
|
26741
|
+
*/
|
|
26742
|
+
async modelled(sessionId, { who, agent }) {
|
|
26743
|
+
const { opencode: opencode2, state: state2 } = this.options;
|
|
26744
|
+
const pick2 = state2.models?.[who];
|
|
26745
|
+
try {
|
|
26746
|
+
const has = (await opencode2.session(sessionId))["model"];
|
|
26747
|
+
const want = pick2 !== void 0 ? refOf(pick2) : agent.model ?? (has === void 0 ? void 0 : (await this.fallbackOf(agent))?.model);
|
|
26748
|
+
if (want === void 0 || sameModel(want, has)) return;
|
|
26749
|
+
await opencode2.setModel(sessionId, want);
|
|
26750
|
+
this.log(`${sessionId} runs on ${writtenOf(want)}${want.variant === void 0 ? "" : ` at ${want.variant}`} from now`);
|
|
26751
|
+
} catch (error62) {
|
|
26752
|
+
if (!(error62 instanceof OpenCodeError && error62.tag === "SessionNotFoundError")) {
|
|
26753
|
+
this.log(`${sessionId} stays on its model: ${messageOf(error62)}`);
|
|
26754
|
+
}
|
|
26755
|
+
}
|
|
26756
|
+
}
|
|
26757
|
+
/** The folder an agent's sessions work in, its own or the one project's; none where the bridge knows neither. */
|
|
26758
|
+
async agentFolder(whose) {
|
|
26759
|
+
return await this.folderFor(whose) ?? this.options.directory;
|
|
26760
|
+
}
|
|
26761
|
+
/**
|
|
26762
|
+
* What OpenCode reads for a folder beside its `AGENTS.md` files: where its walk up stops, and
|
|
26763
|
+
* from 2.0.30 the files its config's `instructions` names. A folder whose project OpenCode can't
|
|
26764
|
+
* say is its own project.
|
|
26765
|
+
*/
|
|
26766
|
+
async readingOf(folder2) {
|
|
26767
|
+
const { opencode: opencode2 } = this.options;
|
|
26768
|
+
const { home: home3 } = this.places;
|
|
26769
|
+
let root = this.roots.get(folder2);
|
|
26770
|
+
if (root === void 0) {
|
|
26771
|
+
root = await opencode2.projectRoot(folder2).catch(() => folder2);
|
|
26772
|
+
this.roots.set(folder2, root);
|
|
26773
|
+
}
|
|
26774
|
+
this.readsNamed ??= opencode2.version().then(
|
|
26775
|
+
(version3) => !before(version3, READS_NAMED),
|
|
26776
|
+
() => false
|
|
26777
|
+
);
|
|
26778
|
+
const sources = await this.readsNamed ? await opencode2.instructions(folder2).catch(() => []) : [];
|
|
26779
|
+
return { stop: within(folder2, home3) ? home3 : root, named: namedFiles(sources, folder2, root, home3) };
|
|
26780
|
+
}
|
|
26781
|
+
/** Each agent of the harness's, with the folder its sessions work in and what OpenCode reads there. */
|
|
26782
|
+
async agentReadings() {
|
|
26783
|
+
const read = [];
|
|
26784
|
+
for (const agent of await this.options.chips.agents.list().catch(() => [])) {
|
|
26785
|
+
if (agent.state === "archived") continue;
|
|
26786
|
+
const whose = await this.whoseIs(agent.agentId);
|
|
26787
|
+
const folder2 = whose === void 0 ? void 0 : await this.agentFolder(whose);
|
|
26788
|
+
if (folder2 !== void 0) read.push({ agentId: agent.agentId, folder: folder2, reading: await this.readingOf(folder2) });
|
|
26789
|
+
}
|
|
26790
|
+
return read;
|
|
26791
|
+
}
|
|
26792
|
+
/** The harness's own set and each agent's files, as they are on disk now: each sent where it changed. */
|
|
26793
|
+
async sendContexts() {
|
|
26794
|
+
const files = this.files;
|
|
26795
|
+
if (files === void 0) return;
|
|
26796
|
+
try {
|
|
26797
|
+
const read = await this.agentReadings();
|
|
26798
|
+
const readings = new Map(read.map((each) => [each.folder, each.reading]));
|
|
26799
|
+
await this.options.chips.settings.harnessContext(files.harnessCopy(readings, read.map((each) => each.folder)));
|
|
26800
|
+
for (const { agentId: agentId2, folder: folder2, reading } of read) {
|
|
26801
|
+
await this.options.chips.settings.context(agentId2, files.agentCopy(folder2, reading));
|
|
26802
|
+
}
|
|
26803
|
+
} catch (error62) {
|
|
26804
|
+
this.log(`the agents' files not sent: ${messageOf(error62)}`);
|
|
26805
|
+
}
|
|
26806
|
+
}
|
|
26807
|
+
/** One agent's files sent, where they changed. */
|
|
26808
|
+
async sendContext(agentId2) {
|
|
26809
|
+
const whose = await this.whoseIs(agentId2);
|
|
26810
|
+
const folder2 = whose === void 0 ? void 0 : await this.agentFolder(whose);
|
|
26811
|
+
if (this.files === void 0 || folder2 === void 0) return;
|
|
26812
|
+
try {
|
|
26813
|
+
await this.options.chips.settings.context(agentId2, this.files.agentCopy(folder2, await this.readingOf(folder2)));
|
|
26814
|
+
} catch (error62) {
|
|
26815
|
+
this.log(`the files of ${agentId2} not sent: ${messageOf(error62)}`);
|
|
26816
|
+
}
|
|
26817
|
+
}
|
|
26818
|
+
/**
|
|
26819
|
+
* A person's edit of a file from Chips (Chips ADR 0134): saved where OpenCode reads it, only if
|
|
26820
|
+
* it is still the version they changed, and answered with the copy as it is now. OpenCode reads
|
|
26821
|
+
* it again before the agent's next model call, and tells the agent what changed: every agent
|
|
26822
|
+
* working in that folder, or for the harness's own set every agent that reads the file.
|
|
26823
|
+
*/
|
|
26824
|
+
async contextEdited(edit) {
|
|
26825
|
+
const files = this.files;
|
|
26826
|
+
if (files === void 0) throw new Refuse("This bridge doesn't know where its agents' files are.");
|
|
26827
|
+
if (edit.agent_id === void 0) {
|
|
26828
|
+
const read = await this.agentReadings();
|
|
26829
|
+
const readings = new Map(read.map((each) => [each.folder, each.reading]));
|
|
26830
|
+
const folders = read.map((each) => each.folder);
|
|
26831
|
+
await files.saveShared(readings, folders, edit);
|
|
26832
|
+
this.log(`OpenCode's ${edit.name} saved from Chips`);
|
|
26833
|
+
return files.harnessCopy(readings, folders);
|
|
26834
|
+
}
|
|
26835
|
+
const whose = await this.whoseIs(edit.agent_id);
|
|
26836
|
+
const folder2 = whose === void 0 ? void 0 : await this.agentFolder(whose);
|
|
26837
|
+
if (whose === void 0 || folder2 === void 0) throw new Refuse("This harness has no such agent.");
|
|
26838
|
+
const reading = await this.readingOf(folder2);
|
|
26839
|
+
await files.saveAgent(folder2, reading, edit);
|
|
26840
|
+
this.log(`${await this.nameOf(whose)}'s ${edit.name} saved from Chips`);
|
|
26841
|
+
for (const other of await this.agentReadings()) {
|
|
26842
|
+
if (other.agentId !== edit.agent_id && other.folder === folder2) void this.sendContext(other.agentId);
|
|
26843
|
+
}
|
|
26844
|
+
return files.agentCopy(folder2, reading);
|
|
26845
|
+
}
|
|
26846
|
+
// ------------------------------------------------------------------ how full each agent is (Chips ADRs 0146, 0241)
|
|
26847
|
+
/** The Chips agent whose sessions are `who`'s: a configured agent's, or the agent itself. */
|
|
26848
|
+
agentIdOf(who) {
|
|
26849
|
+
return this.agents.find((agent) => agent.key === who)?.agentId ?? who;
|
|
26850
|
+
}
|
|
26851
|
+
/** Whose a session is, by the rooms the bridge keeps: nothing for one it didn't make. */
|
|
26852
|
+
whoseSession(sessionId) {
|
|
26853
|
+
const id2 = Object.entries(this.options.state.sessions).find(([, each]) => each === sessionId)?.[0];
|
|
26854
|
+
return id2?.slice(0, id2.lastIndexOf("|"));
|
|
26855
|
+
}
|
|
26856
|
+
/** An agent's window copy sent, and kept with the bridge's state: nothing until it has said how full it is. */
|
|
26857
|
+
async sendWindow(who) {
|
|
26858
|
+
const kept = this.options.state.windows?.[who];
|
|
26859
|
+
if (kept === void 0) return;
|
|
26860
|
+
this.options.saveState(this.options.state);
|
|
26861
|
+
await this.options.chips.settings.contextWindow(this.agentIdOf(who), windowCopy(kept, this.compacting.get(who)?.state)).catch((error62) => this.log(`the context window of ${who} not sent: ${messageOf(error62)}`));
|
|
26862
|
+
}
|
|
26863
|
+
/**
|
|
26864
|
+
* Each agent's window as it last said, at start. One the bridge kept nothing of, from before it
|
|
26865
|
+
* kept them, is read from the newest model call of its sessions.
|
|
26866
|
+
*/
|
|
26867
|
+
async sendWindows() {
|
|
26868
|
+
const { opencode: opencode2, state: state2 } = this.options;
|
|
26869
|
+
const whose = new Set(Object.keys(state2.sessions).map((id2) => id2.slice(0, id2.lastIndexOf("|"))));
|
|
26870
|
+
for (const who of whose) {
|
|
26871
|
+
if (state2.windows?.[who] === void 0) {
|
|
26872
|
+
let newest;
|
|
26873
|
+
for (const [id2, sessionId] of Object.entries(state2.sessions)) {
|
|
26874
|
+
if (!id2.startsWith(`${who}|`)) continue;
|
|
26875
|
+
const call = await opencode2.lastCall(sessionId).catch(() => void 0);
|
|
26876
|
+
if (call !== void 0 && (newest === void 0 || call.at > newest.call.at)) newest = { sessionId, call };
|
|
26877
|
+
}
|
|
26878
|
+
const model = newest?.call.model === void 0 ? void 0 : writtenOf(newest.call.model);
|
|
26879
|
+
const window = model === void 0 ? void 0 : await this.windowOf(model);
|
|
26880
|
+
if (newest === void 0 || model === void 0 || window === void 0) continue;
|
|
26881
|
+
const asOf = new Date(newest.call.at > 0 ? newest.call.at : Date.now()).toISOString();
|
|
26882
|
+
state2.windows = { ...state2.windows, [who]: { used: usedOf(newest.call.tokens), window, model, asOf, session: newest.sessionId } };
|
|
26883
|
+
}
|
|
26884
|
+
await this.sendWindow(who);
|
|
26885
|
+
}
|
|
26886
|
+
}
|
|
26887
|
+
/** A turn ended: how full it got, where that passed half the window, and the copy as it is now. */
|
|
26888
|
+
async turnHeld(turn) {
|
|
26889
|
+
const kept = this.options.state.windows?.[turn.who];
|
|
26890
|
+
if (kept === void 0) return;
|
|
26891
|
+
if (turn.peak > kept.window / 2) {
|
|
26892
|
+
kept.longTurn = { at: (/* @__PURE__ */ new Date()).toISOString(), peak: Math.round(turn.peak), folds: turn.folds };
|
|
26893
|
+
}
|
|
26894
|
+
await this.sendWindow(turn.who);
|
|
26895
|
+
}
|
|
26896
|
+
/**
|
|
26897
|
+
* What OpenCode says of a compaction of one of the agents' sessions: under way, done, with how
|
|
26898
|
+
* much it held before and after, or failed. A Compact now asked from Chips that failed is
|
|
26899
|
+
* refused after all, in OpenCode's words.
|
|
26900
|
+
*/
|
|
26901
|
+
compactionHeard(sessionId, event) {
|
|
26902
|
+
if (event.type === "session.compaction.started") this.rules.forget(sessionId);
|
|
26903
|
+
const who = this.whoseSession(sessionId);
|
|
26904
|
+
const kept = who === void 0 ? void 0 : this.options.state.windows?.[who];
|
|
26905
|
+
if (who === void 0 || kept === void 0) return;
|
|
26906
|
+
const asked = this.compacting.get(who);
|
|
26907
|
+
if (event.type === "session.compaction.started") {
|
|
26908
|
+
if (asked === void 0) this.compacting.set(who, { state: "running" });
|
|
26909
|
+
else asked.state = "running";
|
|
26910
|
+
void this.sendWindow(who);
|
|
26911
|
+
return;
|
|
26912
|
+
}
|
|
26913
|
+
if (event.type === "session.compaction.ended") {
|
|
26914
|
+
this.compacting.delete(who);
|
|
26915
|
+
const to = compactedTo(event.data);
|
|
26916
|
+
const by = event.data["reason"] === "manual" ? "asked" : "itself";
|
|
26917
|
+
kept.last = { at: (/* @__PURE__ */ new Date()).toISOString(), from: Math.round(kept.used), to, by };
|
|
26918
|
+
kept.used = to;
|
|
26919
|
+
kept.asOf = kept.last.at;
|
|
26920
|
+
kept.session = sessionId;
|
|
26921
|
+
const turn = this.turns.get(sessionId);
|
|
26922
|
+
if (turn !== void 0 && by === "itself") turn.folds += 1;
|
|
26923
|
+
this.log(`${who} compacted (${by}), ${kept.last.from} to ${to} tokens`);
|
|
26924
|
+
void this.sendWindow(who);
|
|
26925
|
+
return;
|
|
26926
|
+
}
|
|
26927
|
+
if (event.type === "session.compaction.failed") {
|
|
26928
|
+
this.compacting.delete(who);
|
|
26929
|
+
const error62 = event.data["error"] ?? {};
|
|
26930
|
+
const why = text(error62["message"]) ?? "OpenCode didn't compact it.";
|
|
26931
|
+
this.log(`${who} not compacted: ${why}`);
|
|
26932
|
+
if (asked?.changeId === void 0) {
|
|
26933
|
+
void this.sendWindow(who);
|
|
26934
|
+
return;
|
|
26935
|
+
}
|
|
26936
|
+
void this.options.chips.settings.contextWindowFailed(this.agentIdOf(who), asked.changeId, why, windowCopy(kept)).catch((failed) => this.log(`the context window of ${who} not sent: ${messageOf(failed)}`));
|
|
26937
|
+
}
|
|
26938
|
+
}
|
|
26939
|
+
/**
|
|
26940
|
+
* A person's Compact now (Chips ADR 0146): asked of OpenCode for the session the agent said it
|
|
26941
|
+
* was in last, which takes it at once when idle and at its next step when working, and answered
|
|
26942
|
+
* with the copy saying so. How it goes comes in OpenCode's own events.
|
|
26943
|
+
*/
|
|
26944
|
+
async windowCompact(ask2) {
|
|
26945
|
+
const whose = await this.whoseIs(ask2.agent_id);
|
|
26946
|
+
if (whose === void 0) throw new Refuse("This harness has no such agent.");
|
|
26947
|
+
const { who } = whose;
|
|
26948
|
+
const kept = this.options.state.windows?.[who];
|
|
26949
|
+
if (kept === void 0) throw new Refuse(`${await this.nameOf(whose)} hasn't said how full it is yet.`);
|
|
26950
|
+
if (this.compacting.has(who)) throw new Refuse("It is compacting already.", windowCopy(kept, this.compacting.get(who)?.state));
|
|
26951
|
+
const { opencode: opencode2 } = this.options;
|
|
26952
|
+
const busy = this.turns.has(kept.session) || (await opencode2.active().catch(() => /* @__PURE__ */ new Set())).has(kept.session);
|
|
26953
|
+
this.compacting.set(who, { state: busy ? "queued" : "running", changeId: ask2.change_id });
|
|
26954
|
+
try {
|
|
26955
|
+
await opencode2.compact(kept.session);
|
|
26956
|
+
} catch (error62) {
|
|
26957
|
+
this.compacting.delete(who);
|
|
26958
|
+
throw new Refuse(`OpenCode didn't take it: ${messageOf(error62)}`, windowCopy(kept));
|
|
26959
|
+
}
|
|
26960
|
+
this.log(`${await this.nameOf(whose)} compacts ${busy ? "at its session's next step" : "now"}, from Chips`);
|
|
26961
|
+
return windowCopy(kept, this.compacting.get(who)?.state);
|
|
26962
|
+
}
|
|
26963
|
+
/** A person's limit: OpenCode keeps its own, set in its config, and the copy says so. */
|
|
26964
|
+
async windowChange(ask2) {
|
|
26965
|
+
const whose = await this.whoseIs(ask2.agent_id);
|
|
26966
|
+
const kept = whose === void 0 ? void 0 : this.options.state.windows?.[whose.who];
|
|
26967
|
+
if (whose === void 0) throw new Refuse("This harness has no such agent.");
|
|
26968
|
+
const copy = kept === void 0 ? void 0 : windowCopy(kept, this.compacting.get(whose.who)?.state);
|
|
26969
|
+
throw new Refuse(copy?.why_read_only ?? "OpenCode keeps its own limit.", copy);
|
|
26970
|
+
}
|
|
25996
26971
|
// ------------------------------------------------------------------ what is said to it
|
|
25997
26972
|
/** A message for some of its agents: each takes a turn in its room, one after another there. */
|
|
25998
26973
|
async hear(message, own2 = {}) {
|
|
@@ -26082,16 +27057,18 @@ var Bridge = class {
|
|
|
26082
27057
|
const speaker = speakerId === void 0 ? this.options.chips : this.options.chips.as(speakerId);
|
|
26083
27058
|
const run2 = await speaker.runs.start({ spaceId, goal: goal ?? goalOf(message, agent.config.name) });
|
|
26084
27059
|
const readied = await this.projectFor(room, speakerId ?? agent.agentId);
|
|
26085
|
-
const
|
|
27060
|
+
const asked = {
|
|
26086
27061
|
...speakerId === void 0 ? {} : { taker: speakerId },
|
|
26087
27062
|
sayWho: saysWho !== true,
|
|
26088
27063
|
sendsFiles: true,
|
|
26089
27064
|
project: readied?.facts
|
|
26090
|
-
}
|
|
27065
|
+
};
|
|
27066
|
+
const rules = rulesFor(message, asked);
|
|
27067
|
+
const said = (session) => promptFor(message, { ...asked, rulesHeld: this.rules.holds(session, rules) });
|
|
26091
27068
|
let sessionId;
|
|
26092
27069
|
let given;
|
|
26093
27070
|
try {
|
|
26094
|
-
({ sessionId, given } = await this.give(room,
|
|
27071
|
+
({ sessionId, given } = await this.give(room, said, rules));
|
|
26095
27072
|
} catch (error62) {
|
|
26096
27073
|
await this.failed(run2, `${agent.config.name} couldn't reach OpenCode: ${messageOf(error62)}`, true);
|
|
26097
27074
|
return;
|
|
@@ -26104,6 +27081,10 @@ var Bridge = class {
|
|
|
26104
27081
|
run: run2,
|
|
26105
27082
|
speaker,
|
|
26106
27083
|
agent,
|
|
27084
|
+
who: room.who,
|
|
27085
|
+
agentId: speakerId ?? agent.agentId,
|
|
27086
|
+
peak: 0,
|
|
27087
|
+
folds: 0,
|
|
26107
27088
|
sessionId,
|
|
26108
27089
|
waiting: /* @__PURE__ */ new Set(),
|
|
26109
27090
|
taken: /* @__PURE__ */ new Set(),
|
|
@@ -26135,9 +27116,12 @@ var Bridge = class {
|
|
|
26135
27116
|
if (this.turns.get(sessionId) === turn) this.turns.delete(sessionId);
|
|
26136
27117
|
if (last.outcome === "succeeded") await this.finish(turn);
|
|
26137
27118
|
else await this.ended(turn, last);
|
|
27119
|
+
await this.sendModel(turn.agentId);
|
|
27120
|
+
await this.sendContext(turn.agentId);
|
|
27121
|
+
await this.turnHeld(turn);
|
|
26138
27122
|
}
|
|
26139
27123
|
/** The words into the room's session: the one it had, or a new one when OpenCode no longer has it. */
|
|
26140
|
-
async give({ id: id2, who, agent, spaceId },
|
|
27124
|
+
async give({ id: id2, who, agent, spaceId }, said, rules) {
|
|
26141
27125
|
const { opencode: opencode2, state: state2, saveState } = this.options;
|
|
26142
27126
|
let kept = state2.sessions[id2];
|
|
26143
27127
|
if (kept === void 0 && this.copying.size > 0) {
|
|
@@ -26146,22 +27130,24 @@ var Bridge = class {
|
|
|
26146
27130
|
}
|
|
26147
27131
|
if (kept !== void 0) {
|
|
26148
27132
|
try {
|
|
26149
|
-
|
|
27133
|
+
await this.modelled(kept, { who, agent });
|
|
27134
|
+
return { sessionId: kept, given: await this.prompted(kept, said(kept), rules) };
|
|
26150
27135
|
} catch (error62) {
|
|
26151
27136
|
if (!(error62 instanceof OpenCodeError && error62.tag === "SessionNotFoundError")) throw error62;
|
|
26152
27137
|
this.log(`${agent.config.name}'s session for ${spaceId} is gone from OpenCode: a new one`);
|
|
26153
27138
|
}
|
|
26154
27139
|
}
|
|
27140
|
+
const pick2 = state2.models?.[who];
|
|
26155
27141
|
const made = await opencode2.newSession({
|
|
26156
27142
|
title: `Chips space ${spaceId}`,
|
|
26157
27143
|
agent: agent.config.agent,
|
|
26158
|
-
model: agent.model,
|
|
27144
|
+
model: pick2 === void 0 ? agent.model : refOf(pick2),
|
|
26159
27145
|
directory: await this.folderFor({ who, agent }),
|
|
26160
27146
|
metadata: { chips_space: spaceId, chips_agent: who }
|
|
26161
27147
|
});
|
|
26162
27148
|
state2.sessions[id2] = made;
|
|
26163
27149
|
saveState(state2);
|
|
26164
|
-
return { sessionId: made, given: await this.prompted(made,
|
|
27150
|
+
return { sessionId: made, given: await this.prompted(made, said(made), rules) };
|
|
26165
27151
|
}
|
|
26166
27152
|
/**
|
|
26167
27153
|
* The folder an agent's new sessions work in, its own: made the first time, named after it, and
|
|
@@ -26231,12 +27217,18 @@ var Bridge = class {
|
|
|
26231
27217
|
return known.find((each) => each.agentId === who)?.name ?? "agent";
|
|
26232
27218
|
}
|
|
26233
27219
|
/** A turn's first words into a session, what it says meanwhile held for the turn. */
|
|
26234
|
-
|
|
27220
|
+
/**
|
|
27221
|
+
* A turn's words given to a session, which is told the room's rules they carry as they are
|
|
27222
|
+
* given, so a compaction heard during the turn forgets them (Chips ADR 0250).
|
|
27223
|
+
*/
|
|
27224
|
+
async prompted(sessionId, text2, rules) {
|
|
26235
27225
|
this.early.set(sessionId, []);
|
|
27226
|
+
this.rules.told(sessionId, rules);
|
|
26236
27227
|
try {
|
|
26237
27228
|
return await this.options.opencode.prompt(sessionId, text2);
|
|
26238
27229
|
} catch (error62) {
|
|
26239
27230
|
this.early.delete(sessionId);
|
|
27231
|
+
this.rules.forget(sessionId);
|
|
26240
27232
|
throw error62;
|
|
26241
27233
|
}
|
|
26242
27234
|
}
|
|
@@ -26292,6 +27284,11 @@ var Bridge = class {
|
|
|
26292
27284
|
return;
|
|
26293
27285
|
}
|
|
26294
27286
|
if (event.type === "session.forked") return;
|
|
27287
|
+
if (event.type.startsWith("session.compaction.")) {
|
|
27288
|
+
const going = this.turns.get(sessionId);
|
|
27289
|
+
if (going === void 0) this.compactionHeard(sessionId, event);
|
|
27290
|
+
else going.chain = going.chain.then(() => this.compactionHeard(sessionId, event));
|
|
27291
|
+
}
|
|
26295
27292
|
const own2 = this.turns.get(sessionId);
|
|
26296
27293
|
if (own2 === void 0 && this.early.has(sessionId)) {
|
|
26297
27294
|
this.early.get(sessionId)?.push(event);
|
|
@@ -26463,6 +27460,9 @@ var Bridge = class {
|
|
|
26463
27460
|
const limit = await this.windowOf(model);
|
|
26464
27461
|
if (limit === void 0) return;
|
|
26465
27462
|
await turn.run.context({ usedTokens: prompt + written, limitTokens: limit, model }).catch(() => void 0);
|
|
27463
|
+
const windows = this.options.state.windows ??= {};
|
|
27464
|
+
windows[turn.who] = { ...windows[turn.who], used: prompt + written, window: limit, model, asOf: (/* @__PURE__ */ new Date()).toISOString(), session: turn.sessionId };
|
|
27465
|
+
turn.peak = Math.max(turn.peak, prompt + written);
|
|
26466
27466
|
}
|
|
26467
27467
|
/** A model's context window, from OpenCode's catalogue, read once (and once more for each it lacked). */
|
|
26468
27468
|
async windowOf(model) {
|
|
@@ -26639,6 +27639,21 @@ var Bridge = class {
|
|
|
26639
27639
|
if (retryable) void this.sayHowItIs();
|
|
26640
27640
|
}
|
|
26641
27641
|
};
|
|
27642
|
+
function settingsFor(bridge2) {
|
|
27643
|
+
const active = () => {
|
|
27644
|
+
const running = bridge2();
|
|
27645
|
+
if (running === void 0) throw new Refuse("The harness is still starting.");
|
|
27646
|
+
return running;
|
|
27647
|
+
};
|
|
27648
|
+
return {
|
|
27649
|
+
models: (change) => active().modelChanged(change),
|
|
27650
|
+
context: (edit) => active().contextEdited(edit),
|
|
27651
|
+
contextWindows: {
|
|
27652
|
+
compact: (ask2) => active().windowCompact(ask2),
|
|
27653
|
+
change: (ask2) => active().windowChange(ask2)
|
|
27654
|
+
}
|
|
27655
|
+
};
|
|
27656
|
+
}
|
|
26642
27657
|
function decisionOf(decision, command = false) {
|
|
26643
27658
|
if (!decision.allowed) return "reject";
|
|
26644
27659
|
return decision.scope === "always" && !command ? "always" : "once";
|
|
@@ -26727,6 +27742,12 @@ function spoken(words4) {
|
|
|
26727
27742
|
return end2 === -1 ? words4 : words4.slice(end2 + THOUGHT_ENDS.length);
|
|
26728
27743
|
}
|
|
26729
27744
|
var THOUGHT_ENDS = "</think>";
|
|
27745
|
+
function titleFrom(text2) {
|
|
27746
|
+
const line = text2.trim().split("\n")[0] ?? "";
|
|
27747
|
+
if (line.length <= 48) return line;
|
|
27748
|
+
const cut2 = line.slice(0, 48);
|
|
27749
|
+
return `${cut2.slice(0, Math.max(cut2.lastIndexOf(" "), 24))}\u2026`;
|
|
27750
|
+
}
|
|
26730
27751
|
function forkName(original) {
|
|
26731
27752
|
return `${original.trim().replace(/ · fork( \d+)?$/, "")} \xB7 fork`;
|
|
26732
27753
|
}
|
|
@@ -26791,12 +27812,12 @@ function lastLine(said) {
|
|
|
26791
27812
|
}
|
|
26792
27813
|
|
|
26793
27814
|
// src/keep.ts
|
|
26794
|
-
import { homedir as
|
|
26795
|
-
import { basename as basename3, join as
|
|
27815
|
+
import { homedir as homedir5 } from "node:os";
|
|
27816
|
+
import { basename as basename3, join as join7, resolve as resolve2 } from "node:path";
|
|
26796
27817
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
26797
27818
|
var flags = process.argv.slice(2);
|
|
26798
27819
|
var words3 = flags.filter((word) => !word.startsWith("--"));
|
|
26799
|
-
var whole = (word) => resolve2(word.startsWith("~/") ?
|
|
27820
|
+
var whole = (word) => resolve2(word.startsWith("~/") ? join7(homedir5(), word.slice(2)) : word);
|
|
26800
27821
|
var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
|
|
26801
27822
|
var self = fileURLToPath2(import.meta.url);
|
|
26802
27823
|
var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
|
|
@@ -26814,7 +27835,7 @@ function stopIfAsked(label2) {
|
|
|
26814
27835
|
if (stopped2.state === "theirs") end(`${stopped2.path} wasn't written by this bridge: it was left as it is.`, 1);
|
|
26815
27836
|
end(`Nothing called ${label2} was kept running here.`, 0);
|
|
26816
27837
|
}
|
|
26817
|
-
function keepIfAsked(label2, url2, names, log2,
|
|
27838
|
+
function keepIfAsked(label2, url2, names, log2, before2 = () => void 0) {
|
|
26818
27839
|
if (!flags.includes("--keep")) return;
|
|
26819
27840
|
const node2 = keptNode();
|
|
26820
27841
|
const kept = keepRunning({
|
|
@@ -26824,7 +27845,7 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
|
|
26824
27845
|
environment: keepEnvironment(url2, process.env, names, node2)
|
|
26825
27846
|
});
|
|
26826
27847
|
if (kept.state === "kept") {
|
|
26827
|
-
|
|
27848
|
+
before2();
|
|
26828
27849
|
end(
|
|
26829
27850
|
`Kept running as ${label2}: it starts at login, and again whenever it stops. What it says goes to ${kept.logFile}. \`${stopLine}\` stops it.`,
|
|
26830
27851
|
0
|
|
@@ -26844,9 +27865,9 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
|
|
26844
27865
|
// src/server.ts
|
|
26845
27866
|
import { spawn } from "node:child_process";
|
|
26846
27867
|
import { randomBytes } from "node:crypto";
|
|
26847
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync5, readFileSync as
|
|
27868
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
26848
27869
|
import { createServer } from "node:net";
|
|
26849
|
-
import { delimiter as delimiter2, join as
|
|
27870
|
+
import { delimiter as delimiter2, join as join8 } from "node:path";
|
|
26850
27871
|
import { createInterface } from "node:readline";
|
|
26851
27872
|
var ServerError = class extends Error {
|
|
26852
27873
|
constructor(message, missing = false) {
|
|
@@ -26860,7 +27881,7 @@ var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
|
26860
27881
|
var onPath = (command, env) => command.includes("/") ? existsSync5(command) : (env["PATH"] ?? "").split(delimiter2).some((folder2) => {
|
|
26861
27882
|
if (folder2 === "") return false;
|
|
26862
27883
|
try {
|
|
26863
|
-
accessSync2(
|
|
27884
|
+
accessSync2(join8(folder2, command), constants2.X_OK);
|
|
26864
27885
|
return true;
|
|
26865
27886
|
} catch {
|
|
26866
27887
|
return false;
|
|
@@ -26896,7 +27917,7 @@ async function answers2(url2, password) {
|
|
|
26896
27917
|
function keptIn(path) {
|
|
26897
27918
|
if (!existsSync5(path)) return void 0;
|
|
26898
27919
|
try {
|
|
26899
|
-
const kept = JSON.parse(
|
|
27920
|
+
const kept = JSON.parse(readFileSync6(path, "utf8"));
|
|
26900
27921
|
return typeof kept.port === "number" && typeof kept.password === "string" ? kept : void 0;
|
|
26901
27922
|
} catch {
|
|
26902
27923
|
return void 0;
|
|
@@ -26925,15 +27946,15 @@ async function ownServer(options) {
|
|
|
26925
27946
|
const env = options.env ?? process.env;
|
|
26926
27947
|
const command = options.command ?? "opencode";
|
|
26927
27948
|
const log2 = options.log ?? (() => void 0);
|
|
26928
|
-
const path =
|
|
27949
|
+
const path = join8(options.folder, "server.json");
|
|
26929
27950
|
let kept = keptIn(path);
|
|
26930
|
-
let
|
|
27951
|
+
let before2 = false;
|
|
26931
27952
|
if (kept !== void 0) {
|
|
26932
27953
|
const now = await answers2(`http://127.0.0.1:${kept.port}`, kept.password);
|
|
26933
|
-
|
|
27954
|
+
before2 = now === "ok";
|
|
26934
27955
|
if (now === "refused") kept = { port: await freePort(), password: kept.password };
|
|
26935
27956
|
}
|
|
26936
|
-
if (!
|
|
27957
|
+
if (!before2 && !onPath(command, env)) {
|
|
26937
27958
|
throw new ServerError("OpenCode isn't installed on this machine: install it (opencode.ai), then run this again.", true);
|
|
26938
27959
|
}
|
|
26939
27960
|
kept ??= { port: await freePort(), password: randomBytes(24).toString("base64url") };
|
|
@@ -27016,7 +28037,7 @@ async function ownServer(options) {
|
|
|
27016
28037
|
}
|
|
27017
28038
|
await ended(info["pid"], stopMs);
|
|
27018
28039
|
};
|
|
27019
|
-
if (
|
|
28040
|
+
if (before2) log2(`OpenCode's server from before is still answering at ${url2}`);
|
|
27020
28041
|
else {
|
|
27021
28042
|
await up(false);
|
|
27022
28043
|
log2(`OpenCode's own server answering at ${url2}`);
|
|
@@ -27049,9 +28070,9 @@ async function ownServer(options) {
|
|
|
27049
28070
|
}
|
|
27050
28071
|
|
|
27051
28072
|
// src/setup.ts
|
|
27052
|
-
import { existsSync as existsSync6, readFileSync as
|
|
27053
|
-
import { homedir as
|
|
27054
|
-
import { join as
|
|
28073
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
28074
|
+
import { homedir as homedir6 } from "node:os";
|
|
28075
|
+
import { join as join9, resolve as resolve3 } from "node:path";
|
|
27055
28076
|
var SetupError = class extends Error {
|
|
27056
28077
|
constructor(message, words4 = {}) {
|
|
27057
28078
|
super(message);
|
|
@@ -27068,9 +28089,9 @@ var hostOf = (url2) => {
|
|
|
27068
28089
|
}
|
|
27069
28090
|
};
|
|
27070
28091
|
var STILL_GOOD = "The link is still good: run the same command again once that's done.";
|
|
27071
|
-
var home = (path) => path.startsWith("~/") ?
|
|
28092
|
+
var home = (path) => path.startsWith("~/") ? join9(homedir6(), path.slice(2)) : path;
|
|
27072
28093
|
function readConfig(path, env = process.env) {
|
|
27073
|
-
const raw = existsSync6(path) ? JSON.parse(
|
|
28094
|
+
const raw = existsSync6(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
|
|
27074
28095
|
const passwordEnv = typeof raw["passwordEnv"] === "string" ? raw["passwordEnv"] : "OPENCODE_PASSWORD";
|
|
27075
28096
|
const password = filledIn(env[passwordEnv]) ? env[passwordEnv] : void 0;
|
|
27076
28097
|
const ownServer2 = password === void 0 && typeof raw["url"] !== "string";
|
|
@@ -27125,8 +28146,12 @@ async function checkOpenCode(config3, client) {
|
|
|
27125
28146
|
}
|
|
27126
28147
|
}
|
|
27127
28148
|
|
|
28149
|
+
// src/version.ts
|
|
28150
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
28151
|
+
var VERSION = JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
28152
|
+
|
|
27128
28153
|
// src/main.ts
|
|
27129
|
-
var home2 = (path) => path.startsWith("~/") ?
|
|
28154
|
+
var home2 = (path) => path.startsWith("~/") ? join10(homedir7(), path.slice(2)) : path;
|
|
27130
28155
|
var link = process.env["CHIPS_JOIN_LINK"] || void 0;
|
|
27131
28156
|
var LABEL = "chat.chips.opencode";
|
|
27132
28157
|
stopIfAsked(LABEL);
|
|
@@ -27146,13 +28171,13 @@ try {
|
|
|
27146
28171
|
} catch (error62) {
|
|
27147
28172
|
config2 = stop(error62);
|
|
27148
28173
|
}
|
|
27149
|
-
var folder =
|
|
28174
|
+
var folder = dirname5(configPath);
|
|
27150
28175
|
mkdirSync5(folder, { recursive: true, mode: 448 });
|
|
27151
|
-
var statePath =
|
|
28176
|
+
var statePath = join10(folder, "sessions.json");
|
|
27152
28177
|
var server;
|
|
27153
28178
|
if (link !== void 0) reportStep("checking", { detail: "Looking for OpenCode" });
|
|
27154
28179
|
if (config2.ownServer) {
|
|
27155
|
-
const cwd = config2.directory ??
|
|
28180
|
+
const cwd = config2.directory ?? dirname5(config2.folders);
|
|
27156
28181
|
mkdirSync5(cwd, { recursive: true });
|
|
27157
28182
|
try {
|
|
27158
28183
|
server = await ownServer({ folder, cwd, log });
|
|
@@ -27192,21 +28217,23 @@ var joined = await join3({
|
|
|
27192
28217
|
link,
|
|
27193
28218
|
url: process.env["CHIPS_URL"],
|
|
27194
28219
|
name: config2.name,
|
|
27195
|
-
software:
|
|
28220
|
+
software: `chips-opencode ${VERSION}`,
|
|
27196
28221
|
wants: wantsFor(server !== void 0),
|
|
27197
28222
|
onCode: (code) => log(`Allow this harness in Chips: ${code}`)
|
|
27198
28223
|
}).catch(refused2);
|
|
27199
28224
|
keepIfAsked(LABEL, joined.url, [config2.passwordEnv], log, () => server?.stop());
|
|
27200
|
-
var state = existsSync7(statePath) ? JSON.parse(
|
|
28225
|
+
var state = existsSync7(statePath) ? JSON.parse(readFileSync9(statePath, "utf8")) : { sessions: {} };
|
|
27201
28226
|
var bridge;
|
|
27202
28227
|
var chips = await connect({
|
|
27203
28228
|
url: joined.url,
|
|
27204
28229
|
token: joined.token,
|
|
27205
|
-
client: { name: "chips-opencode", version:
|
|
27206
|
-
store: fileStore(
|
|
28230
|
+
client: { name: "chips-opencode", version: VERSION },
|
|
28231
|
+
store: fileStore(join10(folder, "chips-state.json")),
|
|
27207
28232
|
// A person's Restart (Chips ADR 0094), heard only where the bridge runs OpenCode's server: one a
|
|
27208
28233
|
// person runs themselves isn't the bridge's to restart, so this connection doesn't say it hears them.
|
|
27209
|
-
...server === void 0 ? {} : { onRestart: () => bridge?.restart() }
|
|
28234
|
+
...server === void 0 ? {} : { onRestart: () => bridge?.restart() },
|
|
28235
|
+
// What a person changes of an agent from its page (Chips ADR 0241): its model and its files.
|
|
28236
|
+
settings: settingsFor(() => bridge)
|
|
27210
28237
|
});
|
|
27211
28238
|
var git = await checkGit();
|
|
27212
28239
|
if (!git.ok) log(`rooms' projects are off: ${git.why}`);
|
|
@@ -27221,7 +28248,7 @@ bridge = new Bridge({
|
|
|
27221
28248
|
`, { mode: 384 }),
|
|
27222
28249
|
log,
|
|
27223
28250
|
restartServer: server?.restart,
|
|
27224
|
-
...config2.directory === void 0 ? { folders: config2.folders } : {}
|
|
28251
|
+
...config2.directory === void 0 ? { folders: config2.folders } : { directory: config2.directory }
|
|
27225
28252
|
});
|
|
27226
28253
|
await bridge.start();
|
|
27227
28254
|
log(`answering for ${config2.agents.map((agent) => agent.name).join(", ")} through OpenCode at ${address.url}`);
|