@chipschat/opencode 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +223 -22
- package/package.json +1 -1
|
@@ -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.2 carries these packages, bundled in. Each one's
|
|
4
4
|
licence follows.
|
|
5
5
|
|
|
6
6
|
## @chipschat/harness 0.0.0 (Apache-2.0)
|
package/dist/main.js
CHANGED
|
@@ -20231,6 +20231,29 @@ var projectToken = external_exports.object({
|
|
|
20231
20231
|
expires_at: isoTimestamp,
|
|
20232
20232
|
path: external_exports.string().describe("The project's path on this server, which the device puts its own address in front of.")
|
|
20233
20233
|
});
|
|
20234
|
+
var projectFolderOpenAnswer = external_exports.object({
|
|
20235
|
+
macs: external_exports.int().nonnegative().describe("How many of the person's Macs were asked."),
|
|
20236
|
+
opened_app: external_exports.literal(true).optional().describe("No Chips for Mac of theirs was open, so the server opened the one on its own machine and waited for it (ADR 0227).")
|
|
20237
|
+
});
|
|
20238
|
+
var projectRun = external_exports.object({
|
|
20239
|
+
state: external_exports.enum(["never", "starting", "running", "ended", "failed", "stopped", "opened"]).describe("Never run on that Mac; starting (setup, or waiting for it to answer); running; ended by itself; failed; stopped by the person; or opened, for pages that need no starting."),
|
|
20240
|
+
command: external_exports.string().max(1100).optional().describe("What ran: `setup`, a newline, then `run`, or `open` and the file; the bar compares it with the recipe's."),
|
|
20241
|
+
since: isoTimestamp.optional(),
|
|
20242
|
+
ended: isoTimestamp.optional(),
|
|
20243
|
+
code: external_exports.int().optional().describe("How it ended, as its shell said."),
|
|
20244
|
+
opened: external_exports.string().max(600).optional().describe("What it opened, once it answered."),
|
|
20245
|
+
lines: external_exports.array(external_exports.string().max(400)).max(20).optional().describe("Its last lines, when it ended, or while it runs and something went wrong."),
|
|
20246
|
+
why: external_exports.string().max(400).optional().describe("Why the Mac didn't run it when it was last asked, in its words: no run file, or a different one."),
|
|
20247
|
+
trouble: external_exports.string().max(400).optional().describe("What went wrong with the run itself, in plain words (ADR 0230): a port in use, a program the Mac doesn't have, nothing answering, the browser refusing its address.")
|
|
20248
|
+
});
|
|
20249
|
+
var heldRun = projectRun.extend({
|
|
20250
|
+
mac: external_exports.string().min(1).max(120).describe("The name of the Mac it runs on."),
|
|
20251
|
+
said_at: isoTimestamp
|
|
20252
|
+
});
|
|
20253
|
+
var projectRunAnswer = external_exports.object({
|
|
20254
|
+
run: heldRun.optional(),
|
|
20255
|
+
macs: external_exports.int().nonnegative()
|
|
20256
|
+
});
|
|
20234
20257
|
var commitId = external_exports.string().regex(/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/).describe("A commit, by its full git object id.");
|
|
20235
20258
|
var PROJECT_CHANGED_FILES = 200;
|
|
20236
20259
|
var projectChangedFile = external_exports.object({
|
|
@@ -20261,8 +20284,9 @@ function isSecretsFile(path) {
|
|
|
20261
20284
|
}
|
|
20262
20285
|
var SECRET_SHAPES = [
|
|
20263
20286
|
{ what: "a Chips token", source: "chips_(?:at|inv|j|pt)_[A-Za-z0-9_-]{20,}" },
|
|
20264
|
-
|
|
20265
|
-
{ what: "an
|
|
20287
|
+
// `sk-` begins a word, so a link's words ("…/elon-musk-terafab-project-…") are never a key.
|
|
20288
|
+
{ what: "an Anthropic key", source: "\\bsk-ant-[A-Za-z0-9_-]{20,}" },
|
|
20289
|
+
{ what: "an OpenAI key", source: "\\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{40,}" },
|
|
20266
20290
|
{ what: "an AWS access key", source: "\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b" },
|
|
20267
20291
|
{
|
|
20268
20292
|
what: "a GitHub token",
|
|
@@ -20332,6 +20356,7 @@ function asks(facts, options) {
|
|
|
20332
20356
|
"When your file and another's use different names for the same thing, the one that reached the project first keeps its names, and the other changes its names to match."
|
|
20333
20357
|
);
|
|
20334
20358
|
}
|
|
20359
|
+
lines2.push("When what you make is to be read (pages, PDFs, pictures), keep a start page at .chips/index.html that links to the documents worth seeing (its links start ../), with the documents themselves at the top or in folders of their own, and bring it up to date whenever you add, rename or replace one. Chips opens it in the app, and its links open there too. One document needs no start page.", `When what you make has to run on the person's Mac (a server, a script, an app), keep .chips/run.json so Run in Chips can start it from the project's folder: {"run": "the one shell command that starts it", "opens": "http://localhost:<port>, to open once it answers", "setup": "a command run first when it changes, like npm install"}. Have it print the address it listens on, like http://localhost:4173, which is what Run opens; "opens" is for a program that prints none. Everything it runs belongs in the project: Run refuses a command that goes anywhere outside the project's folder (a cd elsewhere, a path in a home folder). Start an app by its own program rather than with open, so Stop can close it, and leave starting it to Run rather than starting it yourself. Leave out what it doesn't need, and check it works in your copy before you say it does.`);
|
|
20335
20360
|
if (facts.markdown)
|
|
20336
20361
|
lines2.push("In Markdown, put each sentence on a line of its own.");
|
|
20337
20362
|
if (facts.pages === true) {
|
|
@@ -21562,7 +21587,7 @@ var clientHelloFrame = external_exports.object({
|
|
|
21562
21587
|
autonomy: autonomyLevel.optional().describe("The level the harness believes it has.")
|
|
21563
21588
|
})).max(64).optional().describe("Not read by Chips: a connection is its token's, and the server's `hello` names the token's harness and its agents (ADR 0087). Absent for a person's client."),
|
|
21564
21589
|
since: external_exports.int().nonnegative().optional().describe("Not read by Chips: a stream resumes from `subscribe`'s `since`."),
|
|
21565
|
-
accepts: external_exports.array(external_exports.string().min(1).max(40)).max(32).optional().describe("Server frames beyond the base set this client understands, by the names in `SERVER_EXTENSIONS`. A name the server does not know is ignored rather than refused, so a client may name a frame a newer server sends and still be answered by an older one (ADR 0211, Phase 3). A client that says `events` is sent its replay in pages rather than one frame per event; one that says `presence` is sent an agent's state as it changes (ADR 0083); a harness's socket that says `restart` is sent `restart` when a person asks it to restart (ADR 0094); an owner's or admin's that says `join` is sent each join request as it changes (ADR 0122); a harness's that says `context` is sent `context.edit` when a person changes one of its context files (ADR 0134); a harness's that says `models` is sent `model.change` when a person changes an agent's model or effort (ADR 0140); a harness's that says `heartbeats` is sent `heartbeat.change` when a person switches an agent's heartbeat or changes how often it beats (ADR 0143); a harness's that says `context_windows` is sent `context_window.compact` and `context_window.change` when a person asks an agent to compact now or changes the limit it compacts itself at (ADR 0146); one that says nothing is sent what it was always sent, since an unknown frame is dropped in silence."),
|
|
21590
|
+
accepts: external_exports.array(external_exports.string().min(1).max(40)).max(32).optional().describe("Server frames beyond the base set this client understands, by the names in `SERVER_EXTENSIONS`. A name the server does not know is ignored rather than refused, so a client may name a frame a newer server sends and still be answered by an older one (ADR 0211, Phase 3). A client that says `events` is sent its replay in pages rather than one frame per event; one that says `presence` is sent an agent's state as it changes (ADR 0083); a harness's socket that says `restart` is sent `restart` when a person asks it to restart (ADR 0094); an owner's or admin's that says `join` is sent each join request as it changes (ADR 0122); a harness's that says `context` is sent `context.edit` when a person changes one of its context files (ADR 0134); a harness's that says `models` is sent `model.change` when a person changes an agent's model or effort (ADR 0140); a harness's that says `heartbeats` is sent `heartbeat.change` when a person switches an agent's heartbeat or changes how often it beats (ADR 0143); a harness's that says `context_windows` is sent `context_window.compact` and `context_window.change` when a person asks an agent to compact now or changes the limit it compacts itself at (ADR 0146); a person's that says `folders` is sent `folder.open` when they ask, from another of their devices, for a room's folder to be opened in Finder (ADR 0225), and `folder.run` when they ask for its project to be run or stopped there (ADR 0226); a person's that says `folder_runs` is sent `folder.run_state` when their Mac says how a run of theirs stands (ADR 0226); one that says nothing is sent what it was always sent, since an unknown frame is dropped in silence."),
|
|
21566
21591
|
context: contextBudget.optional().describe("How much of a room a harness is handed with each message for it (ADR 0088): 20 lines and 4,000 characters when absent, 200 and 32,000 at most. For this connection. A person's client is handed none.")
|
|
21567
21592
|
}).describe("The first frame a client sends.");
|
|
21568
21593
|
var subscribeFrame = external_exports.object({
|
|
@@ -21623,6 +21648,26 @@ var restartFrame = external_exports.object({
|
|
|
21623
21648
|
asked_by: id("person").describe("The person who asked."),
|
|
21624
21649
|
asked_at: isoTimestamp.describe("When they asked.")
|
|
21625
21650
|
}).describe("A person asks the harness to restart (ADR 0094). Sent to a harness's socket that said `restart` in its `hello`, now or never: it is not in the log, so a reconnect never hears it again. What runs is the harness's own restart.");
|
|
21651
|
+
var folderOpenFrame = external_exports.object({
|
|
21652
|
+
frame: external_exports.literal("folder.open"),
|
|
21653
|
+
space_id: id("space").describe("The room whose folder, or page, to open."),
|
|
21654
|
+
what: external_exports.enum(["folder", "page"]).optional().describe("`page`: open the room's start page, or its one document, in Chips for Mac (ADR 0227). Left out, the folder in Finder."),
|
|
21655
|
+
asked_from: external_exports.string().min(1).max(120).describe("The name of the device that asked: `iPhone`."),
|
|
21656
|
+
asked_at: isoTimestamp.describe("When they asked.")
|
|
21657
|
+
}).describe("A person asks their own Mac to open a room's folder in Finder (ADR 0225), or its page in Chips for Mac (ADR 0227), from another of their devices. Sent to that person's sockets that said `folders` in their `hello`, now or never: it is not in the log. The Mac finds the folder and the page itself: nothing here is a path.");
|
|
21658
|
+
var folderRunFrame = external_exports.object({
|
|
21659
|
+
frame: external_exports.literal("folder.run"),
|
|
21660
|
+
space_id: id("space").describe("The room whose project to run, or stop."),
|
|
21661
|
+
op: external_exports.enum(["run", "stop"]),
|
|
21662
|
+
command: external_exports.string().min(1).max(1100).optional().describe("For `run`, what the asking device showed would run: the Mac runs nothing else, and says so when its folder's run file says something different."),
|
|
21663
|
+
asked_from: external_exports.string().min(1).max(120).describe("The name of the device that asked: `iPhone`."),
|
|
21664
|
+
asked_at: isoTimestamp.describe("When they asked.")
|
|
21665
|
+
}).describe("A person asks their own Mac to run a room's project as its run file says, or to stop it (ADR 0226), from another of their devices. Sent to that person's sockets that said `folders` in their `hello`, now or never: it is not in the log. Nothing here is a command: the Mac reads it from the room's folder.");
|
|
21666
|
+
var folderRunStateFrame = external_exports.object({
|
|
21667
|
+
frame: external_exports.literal("folder.run_state"),
|
|
21668
|
+
space_id: id("space"),
|
|
21669
|
+
run: heldRun
|
|
21670
|
+
}).describe("How a person's run of a room's project on their Mac stands (ADR 0226), as that Mac said it. Sent to that person's sockets that said `folder_runs` in their `hello`, never to anyone else's, now or never: `GET /v1/spaces/:id/project/run` reads it again.");
|
|
21626
21671
|
var joinFrame = external_exports.object({
|
|
21627
21672
|
frame: external_exports.literal("join"),
|
|
21628
21673
|
request: joinRequestView
|
|
@@ -21665,6 +21710,9 @@ var serverFrame = external_exports.discriminatedUnion("frame", [
|
|
|
21665
21710
|
presenceFrame,
|
|
21666
21711
|
restartFrame,
|
|
21667
21712
|
joinFrame,
|
|
21713
|
+
folderOpenFrame,
|
|
21714
|
+
folderRunFrame,
|
|
21715
|
+
folderRunStateFrame,
|
|
21668
21716
|
contextEditFrame,
|
|
21669
21717
|
modelChangeFrame,
|
|
21670
21718
|
heartbeatChangeFrame,
|
|
@@ -21948,6 +21996,7 @@ import { existsSync } from "node:fs";
|
|
|
21948
21996
|
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
21949
21997
|
import { join } from "node:path";
|
|
21950
21998
|
var READ_UP_TO = 4 * 1024 * 1024;
|
|
21999
|
+
var MARKED_LINES = 14;
|
|
21951
22000
|
var TRIES = 4;
|
|
21952
22001
|
var MARKS = /^(<<<<<<<|>>>>>>>) /m;
|
|
21953
22002
|
var REJECTED = /\[rejected\]|fetch first|non-fast-forward/;
|
|
@@ -21986,6 +22035,27 @@ function pushedInWords(pushed) {
|
|
|
21986
22035
|
return `not sent: ${pushed.why}`;
|
|
21987
22036
|
}
|
|
21988
22037
|
}
|
|
22038
|
+
var stopped = (words4) => `${words4.trim().replace(/[.\s]+$/, "")}.`;
|
|
22039
|
+
function heldNote(pushed, name) {
|
|
22040
|
+
switch (pushed.state) {
|
|
22041
|
+
case "sent":
|
|
22042
|
+
case "nothing":
|
|
22043
|
+
return void 0;
|
|
22044
|
+
case "clash": {
|
|
22045
|
+
const whose = pushed.with.length === 0 ? "another change" : `${namesInWords(pushed.with)}'s change`;
|
|
22046
|
+
return `(Not in the room's project yet: ${namesInWords(pushed.files)} clashed with ${whose}. ${name} is asked to settle it next turn.)`;
|
|
22047
|
+
}
|
|
22048
|
+
case "keys": {
|
|
22049
|
+
const [first] = pushed.keys;
|
|
22050
|
+
const held = pushed.keys.length === 1 && first !== void 0 ? `${first.path} looks like it holds ${first.what.replace(/, by its name$/, "")}` : `${namesInWords(pushed.keys.map((key) => key.path))} look like they hold keys`;
|
|
22051
|
+
return `(Not in the room's project: ${held}, so nothing was sent.)`;
|
|
22052
|
+
}
|
|
22053
|
+
case "refused":
|
|
22054
|
+
return `(Not in the room's project: it was turned back: ${stopped(pushed.why)})`;
|
|
22055
|
+
case "failed":
|
|
22056
|
+
return `(Not in the room's project: it couldn't be sent: ${stopped(pushed.why)})`;
|
|
22057
|
+
}
|
|
22058
|
+
}
|
|
21989
22059
|
async function checkGit(git2 = "git", path = process.env["PATH"] ?? "/usr/bin:/bin") {
|
|
21990
22060
|
const [major, minor] = versionIn(PROJECT_GIT_VERSION) ?? [0, 0];
|
|
21991
22061
|
return new Promise((resolve5) => {
|
|
@@ -22005,7 +22075,7 @@ var Project = class {
|
|
|
22005
22075
|
remote;
|
|
22006
22076
|
constructor(options) {
|
|
22007
22077
|
this.options = options;
|
|
22008
|
-
this.copy = join(options.folder, "room");
|
|
22078
|
+
this.copy = options.person === true ? options.folder : join(options.folder, "room");
|
|
22009
22079
|
this.remote = `${options.url.replace(/\/$/, "").replace(/^ws/, "http")}${projectPath(options.spaceId)}`;
|
|
22010
22080
|
}
|
|
22011
22081
|
/** Before a turn: the copy made or brought up to date, and what the turn is to be told of it. */
|
|
@@ -22059,6 +22129,121 @@ var Project = class {
|
|
|
22059
22129
|
}
|
|
22060
22130
|
return { state: "failed", why: "others kept sending first." };
|
|
22061
22131
|
}
|
|
22132
|
+
/**
|
|
22133
|
+
* A person's folder made, or brought up to the room's `main` while nothing in it is theirs: no
|
|
22134
|
+
* file changed or added, nothing waiting to be sent, no clash (ADR 0224). Once something is
|
|
22135
|
+
* theirs, nothing in the folder changes; what others changed since is told instead.
|
|
22136
|
+
*/
|
|
22137
|
+
async look() {
|
|
22138
|
+
await this.ready();
|
|
22139
|
+
const fetched = await this.git(["fetch", "--quiet", "origin"]);
|
|
22140
|
+
const trouble = fetched.ok ? void 0 : remoteWords(fetched.err);
|
|
22141
|
+
const remote = await this.head("refs/remotes/origin/main");
|
|
22142
|
+
await this.settledAsTheirs(remote);
|
|
22143
|
+
const brought = await this.bringUntouched(remote);
|
|
22144
|
+
return { ...await this.standing(remote), ...brought, trouble };
|
|
22145
|
+
}
|
|
22146
|
+
/** The folder moved on to the room's `main` while nothing in it is theirs: what came in, whose. */
|
|
22147
|
+
async bringUntouched(remote) {
|
|
22148
|
+
const mine = await this.head("HEAD");
|
|
22149
|
+
const none = { came: [], cameBy: [] };
|
|
22150
|
+
if (remote === "" || mine === remote || !await this.untouched(mine, remote))
|
|
22151
|
+
return none;
|
|
22152
|
+
const moved = mine === "" ? await this.git(["checkout", "--quiet", "-B", PROJECT_BRANCH, remote]) : await this.git(["merge", "--quiet", "--ff-only", remote]);
|
|
22153
|
+
if (!moved.ok)
|
|
22154
|
+
return none;
|
|
22155
|
+
const from = mine === "" ? await this.emptyTree() : mine;
|
|
22156
|
+
return {
|
|
22157
|
+
came: lines((await this.git(["diff", "--name-only", from, remote])).out),
|
|
22158
|
+
cameBy: await this.authors(mine === "" ? remote : `${mine}..${remote}`)
|
|
22159
|
+
};
|
|
22160
|
+
}
|
|
22161
|
+
/**
|
|
22162
|
+
* A clash the person settled by taking the room's side whole: nothing of theirs is left to send,
|
|
22163
|
+
* so the folder goes back to the room's `main` and is kept up to date again. Every tracked file
|
|
22164
|
+
* is already the room's, and a file git doesn't track is never touched.
|
|
22165
|
+
*/
|
|
22166
|
+
async settledAsTheirs(remote) {
|
|
22167
|
+
if (remote === "" || !existsSync(join(this.copy, ".git", "MERGE_HEAD")))
|
|
22168
|
+
return;
|
|
22169
|
+
if ((await this.marked()).length > 0)
|
|
22170
|
+
return;
|
|
22171
|
+
if (!(await this.git(["diff", "--quiet", remote])).ok)
|
|
22172
|
+
return;
|
|
22173
|
+
await this.git(["reset", "--quiet", "--hard", remote]);
|
|
22174
|
+
}
|
|
22175
|
+
/** Whether nothing in the folder is the person's own: no change, no unsent commit, no clash. */
|
|
22176
|
+
async untouched(mine, remote) {
|
|
22177
|
+
if (existsSync(join(this.copy, ".git", "MERGE_HEAD")))
|
|
22178
|
+
return false;
|
|
22179
|
+
const status = await this.git(["status", "--porcelain", "--untracked-files=all"]);
|
|
22180
|
+
if (!status.ok || status.out.trim() !== "")
|
|
22181
|
+
return false;
|
|
22182
|
+
if (mine === "")
|
|
22183
|
+
return true;
|
|
22184
|
+
return (await this.git(["merge-base", "--is-ancestor", mine, remote])).ok;
|
|
22185
|
+
}
|
|
22186
|
+
/** What the person changed, a clash, and what others changed that the folder hasn't. */
|
|
22187
|
+
async standing(remote) {
|
|
22188
|
+
const mine = await this.head("HEAD");
|
|
22189
|
+
const base = await this.baseOf(mine, remote);
|
|
22190
|
+
const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((line) => {
|
|
22191
|
+
const [added2 = "0", removed = "0", ...path] = line.split(" ");
|
|
22192
|
+
return { path: path.join(" "), added: Number(added2) || 0, removed: Number(removed) || 0 };
|
|
22193
|
+
});
|
|
22194
|
+
const added = lines((await this.git(["ls-files", "--others", "--exclude-standard", "-z"])).out.replace(/\0/g, "\n"));
|
|
22195
|
+
const fresh = await Promise.all(added.map(async (path) => ({ path, added: await this.lineCount(path), removed: 0 })));
|
|
22196
|
+
const changed = [...counted, ...fresh].filter((change) => change.path !== "").sort((a, b) => a.path.localeCompare(b.path));
|
|
22197
|
+
const clashes = await this.marked();
|
|
22198
|
+
const clashedWith = await this.mergingWith();
|
|
22199
|
+
const first = clashes[0];
|
|
22200
|
+
const marked = first === void 0 ? void 0 : await this.markedLines(first);
|
|
22201
|
+
const since = remote === "" || remote === base ? [] : lines((await this.git(["diff", "--name-only", base, remote])).out);
|
|
22202
|
+
const by = since.length === 0 ? [] : await this.authors(mine === "" ? remote : `${base}..${remote}`);
|
|
22203
|
+
return { changed, clashes, clashedWith, marked, since, by };
|
|
22204
|
+
}
|
|
22205
|
+
/**
|
|
22206
|
+
* What the person's changes are measured from: where the folder and the room's `main` parted.
|
|
22207
|
+
* In a clash the room's side is already in the folder, so it is `main` itself: what differs from
|
|
22208
|
+
* it is what Send would change.
|
|
22209
|
+
*/
|
|
22210
|
+
async baseOf(mine, remote) {
|
|
22211
|
+
if (mine === "")
|
|
22212
|
+
return this.emptyTree();
|
|
22213
|
+
if (remote === "")
|
|
22214
|
+
return mine;
|
|
22215
|
+
if (existsSync(join(this.copy, ".git", "MERGE_HEAD")))
|
|
22216
|
+
return remote;
|
|
22217
|
+
return (await this.git(["merge-base", mine, remote])).out.trim() || mine;
|
|
22218
|
+
}
|
|
22219
|
+
/** How many lines a file the person added has: the lines it adds. None for a picture. */
|
|
22220
|
+
async lineCount(path) {
|
|
22221
|
+
try {
|
|
22222
|
+
const at = join(this.copy, path);
|
|
22223
|
+
if ((await stat(at)).size > READ_UP_TO)
|
|
22224
|
+
return 0;
|
|
22225
|
+
const words4 = await readFile(at, "utf8");
|
|
22226
|
+
if (words4.includes("\0"))
|
|
22227
|
+
return 0;
|
|
22228
|
+
return words4 === "" ? 0 : words4.split("\n").length - (words4.endsWith("\n") ? 1 : 0);
|
|
22229
|
+
} catch {
|
|
22230
|
+
return 0;
|
|
22231
|
+
}
|
|
22232
|
+
}
|
|
22233
|
+
/** The first clash in a file, from the line before its first mark to the line after its last. */
|
|
22234
|
+
async markedLines(path) {
|
|
22235
|
+
try {
|
|
22236
|
+
const all = (await readFile(join(this.copy, path), "utf8")).split("\n");
|
|
22237
|
+
const start = all.findIndex((line) => line.startsWith("<<<<<<< "));
|
|
22238
|
+
const end2 = all.findIndex((line, at) => at > start && line.startsWith(">>>>>>> "));
|
|
22239
|
+
if (start < 0 || end2 < 0)
|
|
22240
|
+
return void 0;
|
|
22241
|
+
const shown = all.slice(Math.max(0, start - 1), end2 + 2);
|
|
22242
|
+
return shown.length <= MARKED_LINES ? shown.join("\n") : [...shown.slice(0, MARKED_LINES - 1), "\u2026"].join("\n");
|
|
22243
|
+
} catch {
|
|
22244
|
+
return void 0;
|
|
22245
|
+
}
|
|
22246
|
+
}
|
|
22062
22247
|
/**
|
|
22063
22248
|
* One try at sending: sent, nothing to send, or turned back; or, when somebody sent first, the
|
|
22064
22249
|
* agent's commits replayed on top of theirs, for the next try, unless they clashed.
|
|
@@ -22111,6 +22296,10 @@ ${sent.err}`;
|
|
|
22111
22296
|
const pointed = (await this.git(["remote", "get-url", "origin"])).out.trim();
|
|
22112
22297
|
if (pointed === this.remote)
|
|
22113
22298
|
return false;
|
|
22299
|
+
if (this.options.person === true) {
|
|
22300
|
+
await this.git(["remote", "set-url", "origin", this.remote]);
|
|
22301
|
+
return false;
|
|
22302
|
+
}
|
|
22114
22303
|
const was = /\/v1\/spaces\/([^/]+)\/project\.git$/.exec(pointed)?.[1] ?? "before";
|
|
22115
22304
|
await rename(this.copy, join(this.options.folder, `room-${was}`));
|
|
22116
22305
|
}
|
|
@@ -22264,9 +22453,12 @@ ${sent.err}`;
|
|
|
22264
22453
|
}
|
|
22265
22454
|
git(args) {
|
|
22266
22455
|
const { options } = this;
|
|
22456
|
+
const person = options.person === true;
|
|
22457
|
+
const email3 = `${options.agentId}@${person ? "people" : "agents"}.chips.invalid`;
|
|
22267
22458
|
const pairs = [
|
|
22268
22459
|
["http.extraHeader", `Authorization: Bearer ${options.token}`],
|
|
22269
|
-
|
|
22460
|
+
// A person's project token says who they are; an agent's harness token needs the agent.
|
|
22461
|
+
...person ? [] : [["http.extraHeader", `${PROJECT_ACTOR_HEADER}: ${options.agentId}`]],
|
|
22270
22462
|
["core.hooksPath", "/dev/null"],
|
|
22271
22463
|
["commit.gpgSign", "false"],
|
|
22272
22464
|
["merge.conflictStyle", "merge"],
|
|
@@ -22279,9 +22471,9 @@ ${sent.err}`;
|
|
|
22279
22471
|
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
22280
22472
|
GIT_TERMINAL_PROMPT: "0",
|
|
22281
22473
|
GIT_AUTHOR_NAME: options.name,
|
|
22282
|
-
GIT_AUTHOR_EMAIL:
|
|
22474
|
+
GIT_AUTHOR_EMAIL: email3,
|
|
22283
22475
|
GIT_COMMITTER_NAME: options.name,
|
|
22284
|
-
GIT_COMMITTER_EMAIL:
|
|
22476
|
+
GIT_COMMITTER_EMAIL: email3,
|
|
22285
22477
|
GIT_EDITOR: "true",
|
|
22286
22478
|
GIT_CONFIG_COUNT: String(pairs.length)
|
|
22287
22479
|
};
|
|
@@ -23990,7 +24182,11 @@ import { readFile as readFile3, realpath, stat as stat2 } from "node:fs/promises
|
|
|
23990
24182
|
import { homedir as homedir2 } from "node:os";
|
|
23991
24183
|
import { basename as basename2, isAbsolute, resolve, sep } from "node:path";
|
|
23992
24184
|
import { fileURLToPath } from "node:url";
|
|
23993
|
-
|
|
24185
|
+
function sendingFiles(options = {}) {
|
|
24186
|
+
const which = options.project === true ? "A file that isn't in the room's project" : "A file";
|
|
24187
|
+
return `${which} reaches this room only when you send it: put MEDIA:<path> on a line of its own in your answer, one line for each file, from your own folder. A path, or a link to this machine, reaches nobody: the people and agents here cannot open your folder.`;
|
|
24188
|
+
}
|
|
24189
|
+
var SENDING_FILES = sendingFiles();
|
|
23994
24190
|
var MOST_FILES = 20;
|
|
23995
24191
|
var MOST_FILE_BYTES = 100 * 1024 * 1024;
|
|
23996
24192
|
function filesNamedIn(answer) {
|
|
@@ -24098,7 +24294,7 @@ function promptFor(message, options = {}) {
|
|
|
24098
24294
|
const who = options.sayWho === false ? "" : `${withRole(message.from.display_name, message.from)}: `;
|
|
24099
24295
|
const files = (message.attachments ?? []).map(renderAttachment);
|
|
24100
24296
|
const words4 = [`${who}${message.text}`, ...files].join("\n");
|
|
24101
|
-
const sending = options.sendsFiles === true ?
|
|
24297
|
+
const sending = options.sendsFiles === true ? sendingFiles({ project: options.project !== void 0 }) : void 0;
|
|
24102
24298
|
const replying = renderReplyingTo(message.replying_to, {
|
|
24103
24299
|
sender: { id: message.from.participant_id, name: message.from.display_name },
|
|
24104
24300
|
taker,
|
|
@@ -25061,18 +25257,22 @@ var Bridge = class {
|
|
|
25061
25257
|
}
|
|
25062
25258
|
/**
|
|
25063
25259
|
* What the turn changed in its copy, sent to the room's project before its words are posted: the
|
|
25064
|
-
* commit it left `main` at, for the reply to carry (Chips ADR 0222), when it sent one
|
|
25260
|
+
* commit it left `main` at, for the reply to carry (Chips ADR 0222), when it sent one; else, when
|
|
25261
|
+
* the change didn't go, the line the reply says so in (Chips AP-6).
|
|
25065
25262
|
*/
|
|
25066
25263
|
async sendProject(turn, words4) {
|
|
25067
|
-
if (turn.project === void 0) return
|
|
25264
|
+
if (turn.project === void 0) return {};
|
|
25068
25265
|
const { copy, name } = turn.project;
|
|
25069
25266
|
try {
|
|
25070
25267
|
const pushed = await copy.push({ words: words4 });
|
|
25071
25268
|
this.log(`${name}'s project: ${pushedInWords(pushed)}`);
|
|
25072
|
-
|
|
25269
|
+
if (pushed.state === "sent") return { change: pushed.commit };
|
|
25270
|
+
const note = heldNote(pushed, name);
|
|
25271
|
+
return note === void 0 ? {} : { note };
|
|
25073
25272
|
} catch (error61) {
|
|
25074
25273
|
this.log(`${name}'s project couldn't be sent: ${messageOf(error61)}`);
|
|
25075
|
-
|
|
25274
|
+
const note = heldNote({ state: "failed", why: messageOf(error61) }, name);
|
|
25275
|
+
return note === void 0 ? {} : { note };
|
|
25076
25276
|
}
|
|
25077
25277
|
}
|
|
25078
25278
|
/** Whose session a room's is, by name: the configured agent's, or one Chips knows of. */
|
|
@@ -25341,8 +25541,8 @@ var Bridge = class {
|
|
|
25341
25541
|
lasts: "once",
|
|
25342
25542
|
...own2 && toolCall !== void 0 && turn.tools.get(toolCall)?.open === true ? { stepId: toolCall } : {}
|
|
25343
25543
|
});
|
|
25344
|
-
const
|
|
25345
|
-
const decision = await Promise.race([asking,
|
|
25544
|
+
const stopped2 = new Promise((resolve5) => turn.run.on("cancel", () => resolve5(void 0)));
|
|
25545
|
+
const decision = await Promise.race([asking, stopped2]);
|
|
25346
25546
|
const pending = turn.approvals.get(requestId);
|
|
25347
25547
|
if (decision === void 0 || pending === void 0 || pending.answered) return;
|
|
25348
25548
|
pending.answered = true;
|
|
@@ -25384,8 +25584,8 @@ var Bridge = class {
|
|
|
25384
25584
|
return;
|
|
25385
25585
|
}
|
|
25386
25586
|
const asking = turn.run.question({ questions });
|
|
25387
|
-
const
|
|
25388
|
-
const answer = await Promise.race([asking,
|
|
25587
|
+
const stopped2 = new Promise((resolve5) => turn.run.on("cancel", () => resolve5(void 0)));
|
|
25588
|
+
const answer = await Promise.race([asking, stopped2]);
|
|
25389
25589
|
if (answer === void 0 || answer.timedOut) return;
|
|
25390
25590
|
const filled = {};
|
|
25391
25591
|
for (const field of fields) {
|
|
@@ -25435,10 +25635,11 @@ var Bridge = class {
|
|
|
25435
25635
|
}
|
|
25436
25636
|
turn.allowed.clear();
|
|
25437
25637
|
const answer = spoken(turn.recovered ?? lastWordsOf(turn.words)).trim();
|
|
25438
|
-
const change = await this.sendProject(turn, answer);
|
|
25638
|
+
const { change, note } = await this.sendProject(turn, answer);
|
|
25439
25639
|
const folder2 = await this.folderOf(turn.sessionId);
|
|
25440
25640
|
const { chips: chips2 } = this.options;
|
|
25441
|
-
const
|
|
25641
|
+
const words4 = [answer, note ?? ""].filter((part) => part !== "").join("\n\n");
|
|
25642
|
+
const post = await sendAnswer(words4, {
|
|
25442
25643
|
folders: folder2 === void 0 ? [] : [folder2],
|
|
25443
25644
|
upload: (input2) => chips2.upload(input2)
|
|
25444
25645
|
});
|
|
@@ -25654,9 +25855,9 @@ var end = (line, code) => {
|
|
|
25654
25855
|
};
|
|
25655
25856
|
function stopIfAsked(label2) {
|
|
25656
25857
|
if (!flags.includes("--stop")) return;
|
|
25657
|
-
const
|
|
25658
|
-
if (
|
|
25659
|
-
if (
|
|
25858
|
+
const stopped2 = stopKeeping(label2);
|
|
25859
|
+
if (stopped2.state === "stopped") end(`Stopped ${label2}, and removed ${stopped2.path}.`, 0);
|
|
25860
|
+
if (stopped2.state === "theirs") end(`${stopped2.path} wasn't written by this bridge: it was left as it is.`, 1);
|
|
25660
25861
|
end(`Nothing called ${label2} was kept running here.`, 0);
|
|
25661
25862
|
}
|
|
25662
25863
|
function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipschat/opencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|