@chipschat/opencode 1.0.4 → 1.0.6
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 +29 -2
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +167 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ server (`opencode serve`, 2.0).
|
|
|
11
11
|
- A copy of an agent a person asks for in another room (Fork) is a copy of its session in its own
|
|
12
12
|
room, in the same folder: it remembers what the agent did there, and from then on the two
|
|
13
13
|
remember apart.
|
|
14
|
+
- Restart on the harness's page restarts the OpenCode server the bridge runs (see [Restart](#restart)).
|
|
14
15
|
|
|
15
16
|
Apache-2.0. Not published: a local repository until Chips's SDK is.
|
|
16
17
|
|
|
@@ -27,16 +28,18 @@ Apache-2.0. Not published: a local repository until Chips's SDK is.
|
|
|
27
28
|
| Files in the reply | each `MEDIA:<path>` line of its own in the answer, taken out of the words and sent from the session's folder (the agent's own), as OpenCode says it (each turn is told how); a file outside it is refused, and the reply says so |
|
|
28
29
|
| Cost and tokens | each model call's `session.step.ended`: OpenCode's own cost, as a step's share |
|
|
29
30
|
| The context gauge | the last call's tokens against the model's window from `GET /api/model` |
|
|
30
|
-
| An approval card | `permission.asked` → `POST …/permission/{id}/reply`: `always` when a person said always, else `once
|
|
31
|
+
| An approval card | `permission.asked` → `POST …/permission/{id}/reply`: `always` when a person said always, else `once`, and `once` for a command, which Chips remembers by how each part starts (Chips ADR 0239); `reject` with their reason for a no. One answered in OpenCode's own apps is settled in Chips (ADR 0079) |
|
|
31
32
|
| A question | a `form.created` whose fields are choices or yes-or-no, at most four → `POST …/form/{id}/reply` |
|
|
32
33
|
| Stop | `POST …/interrupt`, and the words OpenCode hasn't taken yet taken back |
|
|
33
34
|
| A steer, a follow-up, an interrupt | a prompt with `delivery: "steer"`; with `"queue"`; interrupt, then a prompt |
|
|
34
35
|
| Fork (Chips ADR 0087): a copy of an agent in another room | `POST /api/session/{id}/fork` of the agent's session in its room, which copies what it remembers; then `PATCH /api/session/{id}` names the copy for its room and marks it as the copy's. The copy is registered after it (*Nova · fork* for Nova), `forkedFrom` the original, for the room asked, and works in the original's folder. An ask heard again registers the same copy; an agent that never spoke in its room is copied as a new one with nothing to remember |
|
|
35
36
|
| Not right? on an agent's reply (Chips ADR 0200), or a 👎 from an older device | a run of the agent who said it, in its session for the room, after its turns there: a prompt with its reply, what wasn't right, and to answer again in its place; the goal is *Looking again:* and the note's first line. A 👍, or any other reaction, is nothing |
|
|
36
37
|
| Trouble on the harness | `GET /api/info` every 30 s |
|
|
38
|
+
| Restart (Chips ADR 0094) | the bridge's own `opencode serve` stopped and started again on the same port and password; then `GET /api/info`: fine, or why it isn't back |
|
|
37
39
|
|
|
38
40
|
OpenCode asks before anything its config and the agent don't allow (its rules fall back to "ask"),
|
|
39
|
-
so its permission asks reach Chips as cards. An `always` there is saved by OpenCode as well
|
|
41
|
+
so its permission asks reach Chips as cards. An `always` there is saved by OpenCode as well; a
|
|
42
|
+
command never gets one, so the next command comes back to Chips, whose rule answers it.
|
|
40
43
|
|
|
41
44
|
When the event stream drops, OpenCode doesn't replay what was missed. When it's back, the bridge
|
|
42
45
|
reads each turn's session: which words were taken, whether it is still working, and how its last
|
|
@@ -52,6 +55,30 @@ Not yet:
|
|
|
52
55
|
bridge that joins with a code asks for `forks` as well.
|
|
53
56
|
- More than one OpenCode server per bridge.
|
|
54
57
|
|
|
58
|
+
## Restart
|
|
59
|
+
|
|
60
|
+
Where the bridge runs OpenCode's server itself (no `url` in the config), a person can restart it
|
|
61
|
+
from the harness's page in Chips (Chips ADR 0094). The bridge stops the server and starts it again
|
|
62
|
+
on the same port and password, so its sessions, which OpenCode keeps on disk, go on where they
|
|
63
|
+
were. A server it took up from before, left running by a bridge that was stopped hard, is stopped
|
|
64
|
+
by the process it says it is. The bridge says nothing more until the server answers again, then
|
|
65
|
+
says it is fine. If the server doesn't come back, it says why, in OpenCode's own words where it has
|
|
66
|
+
them, ends the runs that were under way, and Chips offers Restart again. A restart stops whatever
|
|
67
|
+
OpenCode was doing, as Chips's sheet says.
|
|
68
|
+
|
|
69
|
+
A server you run yourself (`url` in the config) isn't the bridge's to restart. The bridge doesn't
|
|
70
|
+
ask for `restarts` when it joins, and doesn't hear the ask. A harness joined one way and later
|
|
71
|
+
configured the other keeps what it was granted when it joined:
|
|
72
|
+
- Joined running its own server, then given a `url`: Chips still draws Restart, and answers it
|
|
73
|
+
with "Chips can't reach <its name> to restart it". Turn off *Be restarted from Chips* on the
|
|
74
|
+
harness's page.
|
|
75
|
+
- Joined with a `url`, then running its own server: the bridge hears restarts, but Chips draws no
|
|
76
|
+
Restart until you turn on *Be restarted from Chips* on the harness's page.
|
|
77
|
+
|
|
78
|
+
A link carries what the person chose on it, not what the bridge asks for. The gallery's card for
|
|
79
|
+
OpenCode doesn't grant `restarts` yet, so a bridge joined from it gets Restart once you turn it on
|
|
80
|
+
on its page.
|
|
81
|
+
|
|
55
82
|
## From Chips's gallery
|
|
56
83
|
|
|
57
84
|
The card's line is `chips connect opencode '<link>'` (Chips ADR 0218). It downloads this bridge as the npm package
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
|
-
`dist/main.js` in @chipschat/opencode 1.0.
|
|
3
|
+
`dist/main.js` in @chipschat/opencode 1.0.6 carries these packages, bundled in. Each one's
|
|
4
4
|
licence follows.
|
|
5
5
|
|
|
6
6
|
## @chipschat/harness 0.0.0 (Apache-2.0)
|
package/dist/main.js
CHANGED
|
@@ -25698,6 +25698,9 @@ async function failureOf(answer) {
|
|
|
25698
25698
|
|
|
25699
25699
|
// src/bridge.ts
|
|
25700
25700
|
var WANTS = ["agents", "new_agents", "forks"];
|
|
25701
|
+
function wantsFor(ownServer2) {
|
|
25702
|
+
return ownServer2 ? [...WANTS, "restarts"] : WANTS;
|
|
25703
|
+
}
|
|
25701
25704
|
function freeFolder(root, name, taken) {
|
|
25702
25705
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
25703
25706
|
for (let count = 1; ; count += 1) {
|
|
@@ -25708,6 +25711,7 @@ function freeFolder(root, name, taken) {
|
|
|
25708
25711
|
var ids = createIdGenerator();
|
|
25709
25712
|
var QUIET_MS = 1500;
|
|
25710
25713
|
var INTERRUPTING = "interrupting";
|
|
25714
|
+
var TROUBLE_CHARS = 200;
|
|
25711
25715
|
var LOOKS_KEPT = 500;
|
|
25712
25716
|
var REPLY_CHARS = 1500;
|
|
25713
25717
|
var KINDS = {
|
|
@@ -25760,6 +25764,12 @@ var Bridge = class {
|
|
|
25760
25764
|
health;
|
|
25761
25765
|
listening = new AbortController();
|
|
25762
25766
|
connections = 0;
|
|
25767
|
+
/** Opens the event stream again now, rather than once its wait is over. */
|
|
25768
|
+
wake;
|
|
25769
|
+
/** A person's Restart under way: nothing is said of how it is meanwhile (Chips ADR 0094). */
|
|
25770
|
+
restarting;
|
|
25771
|
+
/** Why OpenCode's server isn't there, where the bridge knows more than that it doesn't answer. */
|
|
25772
|
+
why;
|
|
25763
25773
|
/** Register the agents, start hearing OpenCode and what is said in Chips, and say how it is. */
|
|
25764
25774
|
async start() {
|
|
25765
25775
|
const { chips: chips2 } = this.options;
|
|
@@ -25790,6 +25800,37 @@ var Bridge = class {
|
|
|
25790
25800
|
if (this.health !== void 0) clearInterval(this.health);
|
|
25791
25801
|
this.listening.abort();
|
|
25792
25802
|
}
|
|
25803
|
+
/**
|
|
25804
|
+
* A person's Restart (Chips ADR 0094): the server stopped and started again, then how it is,
|
|
25805
|
+
* fine or why not. Asked again meanwhile, it is the same restart. Nothing where the bridge
|
|
25806
|
+
* doesn't run the server, which never says it hears restarts.
|
|
25807
|
+
*/
|
|
25808
|
+
restart() {
|
|
25809
|
+
this.restarting ??= (async () => {
|
|
25810
|
+
try {
|
|
25811
|
+
await this.restartServer();
|
|
25812
|
+
} finally {
|
|
25813
|
+
this.restarting = void 0;
|
|
25814
|
+
}
|
|
25815
|
+
await this.sayHowItIs();
|
|
25816
|
+
})();
|
|
25817
|
+
return this.restarting;
|
|
25818
|
+
}
|
|
25819
|
+
async restartServer() {
|
|
25820
|
+
const again2 = this.options.restartServer;
|
|
25821
|
+
if (again2 === void 0) return;
|
|
25822
|
+
this.log("a person asked for a restart");
|
|
25823
|
+
try {
|
|
25824
|
+
await again2();
|
|
25825
|
+
this.why = void 0;
|
|
25826
|
+
this.wake?.();
|
|
25827
|
+
} catch (error62) {
|
|
25828
|
+
const why = messageOf(error62).replace(/\s+/g, " ").trim();
|
|
25829
|
+
this.why = why.length <= TROUBLE_CHARS ? why : `${why.slice(0, TROUBLE_CHARS - 1)}\u2026`;
|
|
25830
|
+
this.log(`OpenCode's server isn't back: ${why}`);
|
|
25831
|
+
for (const turn of this.turns.values()) turn.end({ outcome: "failed", why: "OpenCode didn't come back after a restart" });
|
|
25832
|
+
}
|
|
25833
|
+
}
|
|
25793
25834
|
/** The Main agent, the first registered, once started: who says the harness's first words. */
|
|
25794
25835
|
get mainAgentId() {
|
|
25795
25836
|
return this.agents[0]?.agentId;
|
|
@@ -25823,11 +25864,17 @@ var Bridge = class {
|
|
|
25823
25864
|
const there = known.find((agent) => agent.agentId !== main2.agentId && agent.spaces.includes(home3));
|
|
25824
25865
|
await chips2.agents.attach(main2.agentId, { spaceId: home3, replacing: there?.agentId ?? chips2.harnessId }).catch((error62) => this.log(`${main2.name} stays out of its home: ${String(error62)}`));
|
|
25825
25866
|
}
|
|
25826
|
-
/**
|
|
25867
|
+
/**
|
|
25868
|
+
* Trouble while the server doesn't answer, and fine once it does (ADR 0083). Nothing while a
|
|
25869
|
+
* restart is under way: its end says (ADR 0094).
|
|
25870
|
+
*/
|
|
25827
25871
|
async sayHowItIs() {
|
|
25872
|
+
if (this.restarting !== void 0) return;
|
|
25828
25873
|
const up = await this.options.opencode.healthy();
|
|
25874
|
+
if (this.restarting !== void 0) return;
|
|
25875
|
+
if (up) this.why = void 0;
|
|
25829
25876
|
const chips2 = this.options.chips;
|
|
25830
|
-
await (up ? chips2.reportOk() : chips2.reportTrouble("OpenCode isn't answering")).catch(() => void 0);
|
|
25877
|
+
await (up ? chips2.reportOk() : chips2.reportTrouble(this.why ?? "OpenCode isn't answering")).catch(() => void 0);
|
|
25831
25878
|
}
|
|
25832
25879
|
/** OpenCode's event stream, opened again whenever it drops, a little later each time it fails. */
|
|
25833
25880
|
async listen() {
|
|
@@ -25841,8 +25888,15 @@ var Bridge = class {
|
|
|
25841
25888
|
if (this.listening.signal.aborted) return;
|
|
25842
25889
|
this.log(`OpenCode's events: ${messageOf(error62)}`);
|
|
25843
25890
|
}
|
|
25844
|
-
await new Promise((resolve5) =>
|
|
25845
|
-
|
|
25891
|
+
const woken = await new Promise((resolve5) => {
|
|
25892
|
+
const timer = setTimeout(() => resolve5(false), wait2);
|
|
25893
|
+
this.wake = () => {
|
|
25894
|
+
clearTimeout(timer);
|
|
25895
|
+
resolve5(true);
|
|
25896
|
+
};
|
|
25897
|
+
});
|
|
25898
|
+
this.wake = void 0;
|
|
25899
|
+
wait2 = woken ? first : Math.min(wait2 * 2, 15e3);
|
|
25846
25900
|
}
|
|
25847
25901
|
}
|
|
25848
25902
|
/**
|
|
@@ -26043,7 +26097,7 @@ var Bridge = class {
|
|
|
26043
26097
|
return;
|
|
26044
26098
|
}
|
|
26045
26099
|
let end2 = () => void 0;
|
|
26046
|
-
const
|
|
26100
|
+
const ended2 = new Promise((resolve5) => {
|
|
26047
26101
|
end2 = resolve5;
|
|
26048
26102
|
});
|
|
26049
26103
|
const turn = {
|
|
@@ -26076,7 +26130,7 @@ var Bridge = class {
|
|
|
26076
26130
|
const words4 = `${steer.from?.display_name ?? "The person"} adds: ${steer.text}`;
|
|
26077
26131
|
void this.steered(turn, words4, steer.mode);
|
|
26078
26132
|
});
|
|
26079
|
-
const last = await
|
|
26133
|
+
const last = await ended2;
|
|
26080
26134
|
await turn.chain;
|
|
26081
26135
|
if (this.turns.get(sessionId) === turn) this.turns.delete(sessionId);
|
|
26082
26136
|
if (last.outcome === "succeeded") await this.finish(turn);
|
|
@@ -26442,7 +26496,12 @@ var Bridge = class {
|
|
|
26442
26496
|
if (decision === void 0 || pending === void 0 || pending.answered) return;
|
|
26443
26497
|
pending.answered = true;
|
|
26444
26498
|
if (decision.allowed && toolCall !== void 0) turn.allowed.set(toolCall, approvalId);
|
|
26445
|
-
await this.options.opencode.replyPermission(
|
|
26499
|
+
await this.options.opencode.replyPermission(
|
|
26500
|
+
askingSession,
|
|
26501
|
+
requestId,
|
|
26502
|
+
decisionOf(decision, card.action.kind === "shell.exec"),
|
|
26503
|
+
decision.allowed ? void 0 : decision.reason
|
|
26504
|
+
).catch((error62) => this.log(`OpenCode didn't take the answer: ${messageOf(error62)}`));
|
|
26446
26505
|
}
|
|
26447
26506
|
/** A permission answered in OpenCode's own apps: settled in Chips, once, so the card cools (ADR 0079). */
|
|
26448
26507
|
async settled(turn, data) {
|
|
@@ -26580,9 +26639,9 @@ var Bridge = class {
|
|
|
26580
26639
|
if (retryable) void this.sayHowItIs();
|
|
26581
26640
|
}
|
|
26582
26641
|
};
|
|
26583
|
-
function decisionOf(decision) {
|
|
26642
|
+
function decisionOf(decision, command = false) {
|
|
26584
26643
|
if (!decision.allowed) return "reject";
|
|
26585
|
-
return decision.scope === "always" ? "always" : "once";
|
|
26644
|
+
return decision.scope === "always" && !command ? "always" : "once";
|
|
26586
26645
|
}
|
|
26587
26646
|
function questionOf(field, title2) {
|
|
26588
26647
|
const prompt = (field.description ?? field.title ?? title2).slice(0, 500);
|
|
@@ -26797,6 +26856,7 @@ var ServerError = class extends Error {
|
|
|
26797
26856
|
missing;
|
|
26798
26857
|
name = "ServerError";
|
|
26799
26858
|
};
|
|
26859
|
+
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
26800
26860
|
var onPath = (command, env) => command.includes("/") ? existsSync5(command) : (env["PATH"] ?? "").split(delimiter2).some((folder2) => {
|
|
26801
26861
|
if (folder2 === "") return false;
|
|
26802
26862
|
try {
|
|
@@ -26817,18 +26877,22 @@ async function freePort() {
|
|
|
26817
26877
|
});
|
|
26818
26878
|
return await tryPort(4096) ?? await tryPort(0) ?? 4096;
|
|
26819
26879
|
}
|
|
26820
|
-
async function
|
|
26880
|
+
async function infoOf(url2, password) {
|
|
26821
26881
|
try {
|
|
26822
26882
|
const answer = await fetch(`${url2}/api/info`, {
|
|
26823
26883
|
headers: { authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` },
|
|
26824
26884
|
signal: AbortSignal.timeout(2e3)
|
|
26825
26885
|
});
|
|
26826
|
-
if (answer.ok) return
|
|
26886
|
+
if (answer.ok) return await answer.json().catch(() => ({})) ?? {};
|
|
26827
26887
|
return answer.status === 401 ? "refused" : "none";
|
|
26828
26888
|
} catch {
|
|
26829
26889
|
return "none";
|
|
26830
26890
|
}
|
|
26831
26891
|
}
|
|
26892
|
+
async function answers2(url2, password) {
|
|
26893
|
+
const info = await infoOf(url2, password);
|
|
26894
|
+
return typeof info === "string" ? info : "ok";
|
|
26895
|
+
}
|
|
26832
26896
|
function keptIn(path) {
|
|
26833
26897
|
if (!existsSync5(path)) return void 0;
|
|
26834
26898
|
try {
|
|
@@ -26838,27 +26902,41 @@ function keptIn(path) {
|
|
|
26838
26902
|
return void 0;
|
|
26839
26903
|
}
|
|
26840
26904
|
}
|
|
26905
|
+
function alive(pid) {
|
|
26906
|
+
try {
|
|
26907
|
+
process.kill(pid, 0);
|
|
26908
|
+
return true;
|
|
26909
|
+
} catch (error62) {
|
|
26910
|
+
return error62.code === "EPERM";
|
|
26911
|
+
}
|
|
26912
|
+
}
|
|
26913
|
+
async function ended(pid, ms) {
|
|
26914
|
+
for (const signal of ["SIGTERM", "SIGKILL"]) {
|
|
26915
|
+
try {
|
|
26916
|
+
process.kill(pid, signal);
|
|
26917
|
+
} catch {
|
|
26918
|
+
return;
|
|
26919
|
+
}
|
|
26920
|
+
for (const by = Date.now() + (signal === "SIGTERM" ? ms : 2e3); alive(pid) && Date.now() < by; ) await sleep(50);
|
|
26921
|
+
if (!alive(pid)) return;
|
|
26922
|
+
}
|
|
26923
|
+
}
|
|
26841
26924
|
async function ownServer(options) {
|
|
26842
26925
|
const env = options.env ?? process.env;
|
|
26843
26926
|
const command = options.command ?? "opencode";
|
|
26844
26927
|
const log2 = options.log ?? (() => void 0);
|
|
26845
26928
|
const path = join7(options.folder, "server.json");
|
|
26846
26929
|
let kept = keptIn(path);
|
|
26930
|
+
let before = false;
|
|
26847
26931
|
if (kept !== void 0) {
|
|
26848
|
-
const
|
|
26849
|
-
|
|
26850
|
-
if (now === "ok") {
|
|
26851
|
-
log2(`OpenCode's server from before is still answering at ${url3}`);
|
|
26852
|
-
return { url: url3, password: kept.password, stop: () => void 0 };
|
|
26853
|
-
}
|
|
26932
|
+
const now = await answers2(`http://127.0.0.1:${kept.port}`, kept.password);
|
|
26933
|
+
before = now === "ok";
|
|
26854
26934
|
if (now === "refused") kept = { port: await freePort(), password: kept.password };
|
|
26855
26935
|
}
|
|
26856
|
-
if (!onPath(command, env)) {
|
|
26936
|
+
if (!before && !onPath(command, env)) {
|
|
26857
26937
|
throw new ServerError("OpenCode isn't installed on this machine: install it (opencode.ai), then run this again.", true);
|
|
26858
26938
|
}
|
|
26859
26939
|
kept ??= { port: await freePort(), password: randomBytes(24).toString("base64url") };
|
|
26860
|
-
writeFileSync4(path, `${JSON.stringify(kept, null, 2)}
|
|
26861
|
-
`, { mode: 384 });
|
|
26862
26940
|
const { port, password } = kept;
|
|
26863
26941
|
const url2 = `http://127.0.0.1:${port}`;
|
|
26864
26942
|
let child;
|
|
@@ -26866,8 +26944,11 @@ async function ownServer(options) {
|
|
|
26866
26944
|
let answered = false;
|
|
26867
26945
|
let failed;
|
|
26868
26946
|
let wait2 = 2e3;
|
|
26947
|
+
let again2;
|
|
26869
26948
|
const said = [];
|
|
26870
26949
|
const start = () => {
|
|
26950
|
+
writeFileSync4(path, `${JSON.stringify({ port, password }, null, 2)}
|
|
26951
|
+
`, { mode: 384 });
|
|
26871
26952
|
const started = spawn(command, ["serve", "--hostname", "127.0.0.1", "--port", String(port)], {
|
|
26872
26953
|
cwd: options.cwd,
|
|
26873
26954
|
env: { ...env, OPENCODE_PASSWORD: password },
|
|
@@ -26889,9 +26970,10 @@ async function ownServer(options) {
|
|
|
26889
26970
|
return;
|
|
26890
26971
|
}
|
|
26891
26972
|
log2(`OpenCode's server stopped (${signal ?? `exit ${code}`}): starting it again in ${wait2 / 1e3} s`);
|
|
26892
|
-
setTimeout(() => {
|
|
26893
|
-
if (!stopping) start();
|
|
26894
|
-
}, wait2)
|
|
26973
|
+
again2 = setTimeout(() => {
|
|
26974
|
+
if (!stopping && child === void 0) start();
|
|
26975
|
+
}, wait2);
|
|
26976
|
+
again2.unref();
|
|
26895
26977
|
wait2 = Math.min(wait2 * 2, 6e4);
|
|
26896
26978
|
});
|
|
26897
26979
|
started.on("error", (error62) => {
|
|
@@ -26900,25 +26982,68 @@ async function ownServer(options) {
|
|
|
26900
26982
|
});
|
|
26901
26983
|
child = started;
|
|
26902
26984
|
};
|
|
26903
|
-
|
|
26904
|
-
|
|
26905
|
-
|
|
26906
|
-
|
|
26907
|
-
|
|
26908
|
-
|
|
26909
|
-
|
|
26910
|
-
|
|
26985
|
+
const up = async (anew) => {
|
|
26986
|
+
stopping = false;
|
|
26987
|
+
answered = false;
|
|
26988
|
+
failed = void 0;
|
|
26989
|
+
said.length = 0;
|
|
26990
|
+
start();
|
|
26991
|
+
const by = Date.now() + (options.waitMs ?? 3e4);
|
|
26992
|
+
while (await answers2(url2, password) !== "ok") {
|
|
26993
|
+
if (failed !== void 0 || Date.now() > by) {
|
|
26994
|
+
stopping = true;
|
|
26995
|
+
child?.kill("SIGTERM");
|
|
26996
|
+
const last = said.slice(-3).join(" ").trim();
|
|
26997
|
+
throw new ServerError(`OpenCode's server didn't start${anew ? " again" : ""} at ${url2}${last === "" ? "" : `: ${last}`}`);
|
|
26998
|
+
}
|
|
26999
|
+
await sleep(250);
|
|
27000
|
+
}
|
|
27001
|
+
answered = true;
|
|
27002
|
+
wait2 = 2e3;
|
|
27003
|
+
};
|
|
27004
|
+
const down = async () => {
|
|
27005
|
+
stopping = true;
|
|
27006
|
+
clearTimeout(again2);
|
|
27007
|
+
const stopMs = options.stopMs ?? 1e4;
|
|
27008
|
+
const pid = child?.pid;
|
|
27009
|
+
if (pid !== void 0) return ended(pid, stopMs);
|
|
27010
|
+
const info = await infoOf(url2, password);
|
|
27011
|
+
if (typeof info === "string") return;
|
|
27012
|
+
if (typeof info["pid"] !== "number" || info["pid"] <= 0 || info["pid"] === process.pid) {
|
|
27013
|
+
throw new ServerError(
|
|
27014
|
+
`OpenCode's server at ${url2} was running before this bridge and doesn't say which process it is: stop it on this machine, then restart again.`
|
|
27015
|
+
);
|
|
26911
27016
|
}
|
|
26912
|
-
await
|
|
27017
|
+
await ended(info["pid"], stopMs);
|
|
27018
|
+
};
|
|
27019
|
+
if (before) log2(`OpenCode's server from before is still answering at ${url2}`);
|
|
27020
|
+
else {
|
|
27021
|
+
await up(false);
|
|
27022
|
+
log2(`OpenCode's own server answering at ${url2}`);
|
|
26913
27023
|
}
|
|
26914
|
-
|
|
26915
|
-
|
|
27024
|
+
let restarting;
|
|
27025
|
+
const restart = async () => {
|
|
27026
|
+
if (!onPath(command, env)) {
|
|
27027
|
+
throw new ServerError("OpenCode isn't installed on this machine any more: install it (opencode.ai), then restart again.", true);
|
|
27028
|
+
}
|
|
27029
|
+
log2(`restarting OpenCode's server at ${url2}`);
|
|
27030
|
+
await down();
|
|
27031
|
+
await up(true);
|
|
27032
|
+
log2(`OpenCode's server is answering again at ${url2}`);
|
|
27033
|
+
};
|
|
26916
27034
|
return {
|
|
26917
27035
|
url: url2,
|
|
26918
27036
|
password,
|
|
26919
27037
|
stop: () => {
|
|
26920
27038
|
stopping = true;
|
|
27039
|
+
clearTimeout(again2);
|
|
26921
27040
|
child?.kill("SIGTERM");
|
|
27041
|
+
},
|
|
27042
|
+
restart: () => {
|
|
27043
|
+
restarting ??= restart().finally(() => {
|
|
27044
|
+
restarting = void 0;
|
|
27045
|
+
});
|
|
27046
|
+
return restarting;
|
|
26922
27047
|
}
|
|
26923
27048
|
};
|
|
26924
27049
|
}
|
|
@@ -27068,20 +27193,24 @@ var joined = await join3({
|
|
|
27068
27193
|
url: process.env["CHIPS_URL"],
|
|
27069
27194
|
name: config2.name,
|
|
27070
27195
|
software: "chips-opencode 1.0.0",
|
|
27071
|
-
wants:
|
|
27196
|
+
wants: wantsFor(server !== void 0),
|
|
27072
27197
|
onCode: (code) => log(`Allow this harness in Chips: ${code}`)
|
|
27073
27198
|
}).catch(refused2);
|
|
27074
27199
|
keepIfAsked(LABEL, joined.url, [config2.passwordEnv], log, () => server?.stop());
|
|
27075
27200
|
var state = existsSync7(statePath) ? JSON.parse(readFileSync7(statePath, "utf8")) : { sessions: {} };
|
|
27201
|
+
var bridge;
|
|
27076
27202
|
var chips = await connect({
|
|
27077
27203
|
url: joined.url,
|
|
27078
27204
|
token: joined.token,
|
|
27079
27205
|
client: { name: "chips-opencode", version: "1.0.0" },
|
|
27080
|
-
store: fileStore(join9(folder, "chips-state.json"))
|
|
27206
|
+
store: fileStore(join9(folder, "chips-state.json")),
|
|
27207
|
+
// A person's Restart (Chips ADR 0094), heard only where the bridge runs OpenCode's server: one a
|
|
27208
|
+
// 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() }
|
|
27081
27210
|
});
|
|
27082
27211
|
var git = await checkGit();
|
|
27083
27212
|
if (!git.ok) log(`rooms' projects are off: ${git.why}`);
|
|
27084
|
-
|
|
27213
|
+
bridge = new Bridge({
|
|
27085
27214
|
chips,
|
|
27086
27215
|
opencode,
|
|
27087
27216
|
projects: git.ok,
|
|
@@ -27091,6 +27220,7 @@ var bridge = new Bridge({
|
|
|
27091
27220
|
saveState: (next) => writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
|
|
27092
27221
|
`, { mode: 384 }),
|
|
27093
27222
|
log,
|
|
27223
|
+
restartServer: server?.restart,
|
|
27094
27224
|
...config2.directory === void 0 ? { folders: config2.folders } : {}
|
|
27095
27225
|
});
|
|
27096
27226
|
await bridge.start();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipschat/opencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "OpenCode as a Chips harness, on its V2 server (opencode serve): each OpenCode agent a Chips agent, each room an OpenCode session.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|