@nyte-ai/host 0.0.23 → 0.0.25-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/THIRD-PARTY-NOTICES.md +2 -38
- package/dist/chunks/{canvas-preview-d97wvref.js → canvas-preview-1r9sgbj3.js} +1 -1
- package/dist/chunks/{canvas-preview-gm0frf4b.js → canvas-preview-2yzyt077.js} +14 -0
- package/dist/chunks/{canvas-preview-whfdhma6.js → canvas-preview-8sv3j0vt.js} +1 -1
- package/dist/chunks/{canvas-preview-e3vnkkc5.js → canvas-preview-9bxhwjrh.js} +2 -1
- package/dist/chunks/{canvas-preview-2jwbe14k.js → canvas-preview-bg63k8n7.js} +113 -21
- package/dist/chunks/{canvas-preview-9e15hp6q.js → canvas-preview-da6dwy05.js} +4 -3
- package/dist/chunks/{canvas-preview-ayayyd43.js → canvas-preview-dt89p9pm.js} +148 -6
- package/dist/src/durable-object.d.ts +1 -1
- package/dist/src/index.js +7 -7
- package/dist/src/listen-fetch.d.ts +1 -1
- package/dist/src/listen-fetch.js +3 -3
- package/dist/src/listen.js +4 -4
- package/dist/src/local/provider.d.ts +1 -1
- package/dist/src/local/provider.js +2 -2
- package/dist/src/local/worktrees.d.ts +7 -3
- package/dist/src/local/worktrees.js +1 -2
- package/dist/src/open.js +7 -7
- package/dist/src/serve/binding.d.ts +0 -1
- package/dist/src/serve/binding.js +1 -1
- package/dist/src/serve/handler.js +2 -2
- package/package.json +8 -31
- package/src/durable-object.ts +1 -1
- package/src/listen-fetch.ts +6 -3
- package/src/local/provider.ts +3 -1
- package/src/local/worktrees.ts +165 -30
- package/src/open.ts +7 -1
- package/src/serve/binding.ts +18 -1
- package/src/usage-history.ts +0 -1
- package/dist/chunks/canvas-preview-1f8h0he1.js +0 -81
- package/dist/chunks/canvas-preview-2zdyh9ny.js +0 -26
- package/dist/chunks/canvas-preview-65xqjw7f.js +0 -20
- package/dist/chunks/canvas-preview-dsb5829c.js +0 -166
- package/dist/chunks/canvas-preview-kzmp015w.js +0 -36
- package/dist/src/deploy/cloudflare/worker.d.ts +0 -50
- package/dist/src/deploy/cloudflare/worker.js +0 -196
- package/dist/src/deploy/vercel/nitro.config.d.ts +0 -2
- package/dist/src/deploy/vercel/nitro.config.js +0 -30
- package/dist/src/deploy/vercel/server/routes/[...].d.ts +0 -2
- package/dist/src/deploy/vercel/server/routes/[...].js +0 -18
- package/dist/src/deploy/vercel/server/utils/host.d.ts +0 -13
- package/dist/src/deploy/vercel/server/utils/host.js +0 -7
- package/dist/src/deploy/vercel/server/utils/repair.d.ts +0 -1
- package/dist/src/deploy/vercel/server/utils/repair.js +0 -6
- package/dist/src/deploy/vercel/server/utils/step.d.ts +0 -10
- package/dist/src/deploy/vercel/server/utils/step.js +0 -6
- package/dist/src/deploy/vercel/server/workflows/drive.d.ts +0 -14
- package/dist/src/deploy/vercel/server/workflows/drive.js +0 -13
- package/src/deploy/cloudflare/README.md +0 -3
- package/src/deploy/cloudflare/worker.ts +0 -306
- package/src/deploy/cloudflare/wrangler.jsonc +0 -12
- package/src/deploy/vercel/README.md +0 -3
- package/src/deploy/vercel/nitro.config.ts +0 -29
- package/src/deploy/vercel/server/routes/[...].ts +0 -10
- package/src/deploy/vercel/server/utils/host.ts +0 -104
- package/src/deploy/vercel/server/utils/repair.ts +0 -35
- package/src/deploy/vercel/server/utils/step.ts +0 -25
- package/src/deploy/vercel/server/workflows/drive.ts +0 -46
- package/src/deploy/vercel/tsconfig.json +0 -9
- package/src/deploy/vercel/types/bun.d.ts +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
localProvider2
|
|
3
|
-
} from "../../chunks/canvas-preview-
|
|
4
|
-
import"../../chunks/canvas-preview-
|
|
3
|
+
} from "../../chunks/canvas-preview-9bxhwjrh.js";
|
|
4
|
+
import"../../chunks/canvas-preview-bg63k8n7.js";
|
|
5
5
|
import"../../chunks/canvas-preview-5ekjx6sw.js";
|
|
6
6
|
import"../../chunks/canvas-preview-8xc0vn7v.js";
|
|
7
7
|
import"../../chunks/canvas-preview-1sq7z9jq.js";
|
|
@@ -22,11 +22,15 @@ export declare function hasBranch(cwd: string, name: string, options?: {
|
|
|
22
22
|
}): Promise<boolean>;
|
|
23
23
|
/**
|
|
24
24
|
* Returns git worktrees for local workspaces (`git`, default `git` on the
|
|
25
|
-
* PATH), each under `<home>/.nyte/worktrees/<repository>/<
|
|
26
|
-
* `
|
|
25
|
+
* PATH), each under `<home>/.nyte/worktrees/<repository>/<branch>` on a branch
|
|
26
|
+
* named by `branchName`: at most three hyphenated words from the title.
|
|
27
27
|
*
|
|
28
28
|
* `create` refuses a folder outside a repository or one without a commit, then
|
|
29
|
-
* answers the worktree
|
|
29
|
+
* answers the worktree on the branch whose `branch.<branch>.nyte-session`
|
|
30
|
+
* config is `name`, or claims the first name with no folder or listed worktree
|
|
31
|
+
* at its path by creating its branch at HEAD (git refuses an existing branch,
|
|
32
|
+
* which also settles a race with a concurrent create: the loser tries the next
|
|
33
|
+
* name), records `name` in that config, and adds a worktree for it; then it
|
|
30
34
|
* copies the workspace's uncommitted changes in: the worktree's tracked and
|
|
31
35
|
* untracked files, ignored ones aside, are written to a tree through a
|
|
32
36
|
* temporary index seeded from the real one (which is never touched), and the
|
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
addWorktree2,
|
|
4
4
|
hasBranch2,
|
|
5
5
|
localWorktrees2
|
|
6
|
-
} from "../../chunks/canvas-preview-
|
|
6
|
+
} from "../../chunks/canvas-preview-bg63k8n7.js";
|
|
7
7
|
import"../../chunks/canvas-preview-bxnqt8cd.js";
|
|
8
|
-
import"../../chunks/canvas-preview-0nq75h9k.js";
|
|
9
8
|
export {
|
|
10
9
|
addWorktree2 as addWorktree,
|
|
11
10
|
hasBranch2 as hasBranch,
|
package/dist/src/open.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
openHost2
|
|
3
|
-
} from "../chunks/canvas-preview-
|
|
3
|
+
} from "../chunks/canvas-preview-dt89p9pm.js";
|
|
4
4
|
import"../chunks/canvas-preview-4e0pxa32.js";
|
|
5
5
|
import"../chunks/canvas-preview-y5y6ghaw.js";
|
|
6
6
|
import"../chunks/canvas-preview-ec2mt7es.js";
|
|
7
7
|
import"../chunks/canvas-preview-befkjea2.js";
|
|
8
8
|
import"../chunks/canvas-preview-9aqczacs.js";
|
|
9
|
-
import"../chunks/canvas-preview-
|
|
10
|
-
import"../chunks/canvas-preview-
|
|
9
|
+
import"../chunks/canvas-preview-9bxhwjrh.js";
|
|
10
|
+
import"../chunks/canvas-preview-bg63k8n7.js";
|
|
11
11
|
import"../chunks/canvas-preview-5ekjx6sw.js";
|
|
12
12
|
import"../chunks/canvas-preview-8xc0vn7v.js";
|
|
13
13
|
import"../chunks/canvas-preview-1sq7z9jq.js";
|
|
@@ -26,10 +26,10 @@ import"../chunks/canvas-preview-s94ebwf5.js";
|
|
|
26
26
|
import"../chunks/canvas-preview-kdqgrstc.js";
|
|
27
27
|
import"../chunks/canvas-preview-mstbvwz3.js";
|
|
28
28
|
import"../chunks/canvas-preview-zaaj99wd.js";
|
|
29
|
-
import"../chunks/canvas-preview-
|
|
30
|
-
import"../chunks/canvas-preview-
|
|
31
|
-
import"../chunks/canvas-preview-
|
|
32
|
-
import"../chunks/canvas-preview-
|
|
29
|
+
import"../chunks/canvas-preview-8sv3j0vt.js";
|
|
30
|
+
import"../chunks/canvas-preview-da6dwy05.js";
|
|
31
|
+
import"../chunks/canvas-preview-1r9sgbj3.js";
|
|
32
|
+
import"../chunks/canvas-preview-2yzyt077.js";
|
|
33
33
|
import"../chunks/canvas-preview-rp4x6y1h.js";
|
|
34
34
|
import"../chunks/canvas-preview-skzwvw4d.js";
|
|
35
35
|
import"../chunks/canvas-preview-bxnqt8cd.js";
|
|
@@ -34,6 +34,5 @@ export type Binding = CallBinding & FrameBinding & ByteBinding & OutputBinding;
|
|
|
34
34
|
export type BindingOptions = {
|
|
35
35
|
readonly server: ServerHost;
|
|
36
36
|
};
|
|
37
|
-
/** Returns each row bound to the call it names: ids bind handles on the connection, handles returned become ids, `Set`s and `Map`s become their wire forms; `head.move`, `files.batch` and `browser.open` go through the server port's wire operations with their wire id (§40o, §40x). `head.watch` sends the head's view from the host's fold whole, then each later view as its changes (`viewFrames`), the core watch marking the principal's seen tip (§40a, §54a); `sessions.watch` sends the list whole, then each later list as its order and changed rows (`sessionsFrames`). Authority is the caller's gate: the server checks each row's before binding. */
|
|
38
37
|
export declare function bindConnection(connection: Connection, options: BindingOptions): Binding;
|
|
39
38
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createHandler2
|
|
3
|
-
} from "../../chunks/canvas-preview-
|
|
4
|
-
import"../../chunks/canvas-preview-
|
|
3
|
+
} from "../../chunks/canvas-preview-1r9sgbj3.js";
|
|
4
|
+
import"../../chunks/canvas-preview-2yzyt077.js";
|
|
5
5
|
import"../../chunks/canvas-preview-rp4x6y1h.js";
|
|
6
6
|
export {
|
|
7
7
|
createHandler2 as createHandler
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyte-ai/host",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25-beta.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -79,14 +79,6 @@
|
|
|
79
79
|
"./usage-worker": {
|
|
80
80
|
"types": "./dist/src/usage-worker.d.ts",
|
|
81
81
|
"import": "./dist/src/usage-worker.js"
|
|
82
|
-
},
|
|
83
|
-
"./deploy/cloudflare": {
|
|
84
|
-
"types": "./dist/src/deploy/cloudflare/worker.d.ts",
|
|
85
|
-
"import": "./dist/src/deploy/cloudflare/worker.js"
|
|
86
|
-
},
|
|
87
|
-
"./deploy/vercel": {
|
|
88
|
-
"types": "./dist/src/deploy/vercel/nitro.config.d.ts",
|
|
89
|
-
"import": "./dist/src/deploy/vercel/nitro.config.js"
|
|
90
82
|
}
|
|
91
83
|
},
|
|
92
84
|
"publishConfig": {
|
|
@@ -96,11 +88,11 @@
|
|
|
96
88
|
"dependencies": {
|
|
97
89
|
"@earendil-works/pi-mcp": "^1.3.0",
|
|
98
90
|
"@lydell/node-pty": "^1.2.0-beta.15",
|
|
99
|
-
"@nyte-ai/ai": "0.0.
|
|
100
|
-
"@nyte-ai/core": "0.0.
|
|
101
|
-
"@nyte-ai/plugin": "0.0.
|
|
102
|
-
"@nyte-ai/protocol": "0.0.
|
|
103
|
-
"@nyte-ai/schema": "0.0.
|
|
91
|
+
"@nyte-ai/ai": "0.0.25-beta.1",
|
|
92
|
+
"@nyte-ai/core": "0.0.25-beta.1",
|
|
93
|
+
"@nyte-ai/plugin": "0.0.25-beta.1",
|
|
94
|
+
"@nyte-ai/protocol": "0.0.25-beta.1",
|
|
95
|
+
"@nyte-ai/schema": "0.0.25-beta.1",
|
|
104
96
|
"@silvia-odwyer/photon": "^0.3.3",
|
|
105
97
|
"fflate": "^0.8.3",
|
|
106
98
|
"ignore": "^7.0.13",
|
|
@@ -113,29 +105,14 @@
|
|
|
113
105
|
"@types/node": "^26.6.5",
|
|
114
106
|
"@types/pg": "^8.23.1",
|
|
115
107
|
"fast-check": "^4.10.2",
|
|
116
|
-
"
|
|
117
|
-
"typescript": "6.0.3",
|
|
118
|
-
"vitest": "^5.0.5",
|
|
119
|
-
"workflow": "^5.2.3"
|
|
120
|
-
},
|
|
121
|
-
"peerDependencies": {
|
|
122
|
-
"nitro": "^3.0.260903-beta",
|
|
123
|
-
"workflow": "^5.2.3"
|
|
124
|
-
},
|
|
125
|
-
"peerDependenciesMeta": {
|
|
126
|
-
"nitro": {
|
|
127
|
-
"optional": true
|
|
128
|
-
},
|
|
129
|
-
"workflow": {
|
|
130
|
-
"optional": true
|
|
131
|
-
}
|
|
108
|
+
"vitest": "^5.0.5"
|
|
132
109
|
},
|
|
133
110
|
"engines": {
|
|
134
111
|
"node": ">=26.4.0"
|
|
135
112
|
},
|
|
136
113
|
"scripts": {
|
|
137
114
|
"clean": "rm -rf node_modules .turbo",
|
|
138
|
-
"typecheck": "
|
|
115
|
+
"typecheck": "tsc --noEmit",
|
|
139
116
|
"test": "vitest --run",
|
|
140
117
|
"check:store": "node scripts/check-store.ts",
|
|
141
118
|
"build:package": "bun ../../scripts/sdk-packages.mjs build host"
|
package/src/durable-object.ts
CHANGED
|
@@ -44,7 +44,7 @@ const ROW_LIMIT = 2_000_000;
|
|
|
44
44
|
|
|
45
45
|
const encoder = new TextEncoder();
|
|
46
46
|
|
|
47
|
-
/** Returns a `Store` over a Durable Object's SQLite storage, node-free, with the sqlite store's tables, row helpers (`store-rows.ts`) and semantics (§41f): `objects(id TEXT PRIMARY KEY, body TEXT NULL, bytes BLOB NULL)`, `refs(name TEXT PRIMARY KEY, target TEXT NULL, revision TEXT NOT NULL)` and `log(session TEXT, seq INTEGER, at INTEGER, event TEXT, PRIMARY KEY (session, seq))`, brought to core's `STORE_VERSION` by `migrateStore` on first `open()`, each commit one `transactionSync` and no backup; the open rejects with `SdkError("unavailable")` for a store a newer Nyte migrated. `put` ids the value with core's `objectId` and stores its JSON once; `putBlob` buffers the stream, ids it with `blobId` and stores the bytes once as an `ArrayBuffer`; an object and a blob that share an id share the row, each column filled once; `blob` reads the bytes back with their size and a null mime; a body, blob or logged event past the Durable Object's 2 MB row limit throws `SdkError("invalid")` before anything is written (§42k). `refs.set` runs inside one `transactionSync` with no await between its reads and writes, first rejecting with `SdkError("unavailable")` when a newer Nyte has migrated the store, with `RefStore.set`'s semantics, each revision a random UUID, any throw inside rolling the whole batch back. `list(prefix)` reads the name range from `prefix` in name order; `log` returns the events after `after` in seq order up to `limit`, each validated as a `SessionEvent`; `tail` is the session's last seq, or 0. Every `open()` returns a connection over the one storage; `close()` releases nothing, the storage being the object's. In workerd, which allows code generation only while modules load, the Worker turns TypeBox acceleration off at startup (`Settings.Set({ useAcceleration: false })` from `typebox/system`, as `
|
|
47
|
+
/** Returns a `Store` over a Durable Object's SQLite storage, node-free, with the sqlite store's tables, row helpers (`store-rows.ts`) and semantics (§41f): `objects(id TEXT PRIMARY KEY, body TEXT NULL, bytes BLOB NULL)`, `refs(name TEXT PRIMARY KEY, target TEXT NULL, revision TEXT NOT NULL)` and `log(session TEXT, seq INTEGER, at INTEGER, event TEXT, PRIMARY KEY (session, seq))`, brought to core's `STORE_VERSION` by `migrateStore` on first `open()`, each commit one `transactionSync` and no backup; the open rejects with `SdkError("unavailable")` for a store a newer Nyte migrated. `put` ids the value with core's `objectId` and stores its JSON once; `putBlob` buffers the stream, ids it with `blobId` and stores the bytes once as an `ArrayBuffer`; an object and a blob that share an id share the row, each column filled once; `blob` reads the bytes back with their size and a null mime; a body, blob or logged event past the Durable Object's 2 MB row limit throws `SdkError("invalid")` before anything is written (§42k). `refs.set` runs inside one `transactionSync` with no await between its reads and writes, first rejecting with `SdkError("unavailable")` when a newer Nyte has migrated the store, with `RefStore.set`'s semantics, each revision a random UUID, any throw inside rolling the whole batch back. `list(prefix)` reads the name range from `prefix` in name order; `log` returns the events after `after` in seq order up to `limit`, each validated as a `SessionEvent`; `tail` is the session's last seq, or 0. Every `open()` returns a connection over the one storage; `close()` releases nothing, the storage being the object's. In workerd, which allows code generation only while modules load, the Worker turns TypeBox acceleration off at startup (`Settings.Set({ useAcceleration: false })` from `typebox/system`, as `packages/examples/cloudflare/worker.ts` does), or `log`'s validation throws inside a request. */
|
|
48
48
|
export function durableObjectStore(storage: DurableObjectSql): Store {
|
|
49
49
|
let connection: StoreConnection | null = null;
|
|
50
50
|
|
package/src/listen-fetch.ts
CHANGED
|
@@ -36,7 +36,7 @@ type Device = {
|
|
|
36
36
|
|
|
37
37
|
const encoder = new TextEncoder();
|
|
38
38
|
|
|
39
|
-
/** Serves a composed host as a bare fetch handler with no socket and no `node:*` import, so a Worker can serve it (§41j): `before`, the unauthenticated identity and describe routes, then `createHandler` over one cached connection per principal and authority, every response carrying `X-Nyte-Host` (§43a, §43j). A request authenticates by a pairing code, then the listener token (compared in constant time over SHA-256 digests) or a device token remembered until a commit from another process moves the host's principals, then `authorize` under `custom`; a revoke aborts that principal's open requests and streams. A `token` shorter than 16 characters throws `SdkError("invalid")`; `execution` (default: run covering all) drives covered sessions in process on an `admit-only` host, covers their leases otherwise and takes nothing on a `dispatch` host (§42v); `close()` disconnects the clients and releases the execution once, after which every request answers `unavailable`. */
|
|
39
|
+
/** Serves a composed host as a bare fetch handler with no socket and no `node:*` import, so a Worker can serve it (§41j): `before`, the unauthenticated identity and describe routes, then `createHandler` over one cached connection per principal and authority, every response carrying `X-Nyte-Host` (§43a, §43j). A request authenticates by a pairing code, then the listener token (compared in constant time over SHA-256 digests, granting `auth.authority`) or a device token remembered until a commit from another process moves the host's principals, then `authorize` under `custom`; a revoke aborts that principal's open requests and streams. A `token` shorter than 16 characters throws `SdkError("invalid")`; `execution` (default: run covering all) drives covered sessions in process on an `admit-only` host, covers their leases otherwise and takes nothing on a `dispatch` host (§42v); `close()` disconnects the clients and releases the execution once, after which every request answers `unavailable`. */
|
|
40
40
|
export async function listenFetch(
|
|
41
41
|
host: ServedHost,
|
|
42
42
|
options: ListenOptions & { readonly transport: { readonly kind: "fetch" } },
|
|
@@ -161,6 +161,9 @@ export async function listenFetch(
|
|
|
161
161
|
const isListenerToken: (token: string) => Promise<boolean> =
|
|
162
162
|
auth.kind === "token" ? (token) => sameSecret(token, auth.token) : async () => false;
|
|
163
163
|
|
|
164
|
+
const listenerAuthority =
|
|
165
|
+
auth.kind === "token" ? (auth.authority ?? TOKEN_AUTHORITY) : TOKEN_AUTHORITY;
|
|
166
|
+
|
|
164
167
|
const fallback: (request: Request, token: string | undefined) => Promise<Verdict> =
|
|
165
168
|
auth.kind === "custom"
|
|
166
169
|
? async (request) => {
|
|
@@ -207,8 +210,8 @@ export async function listenFetch(
|
|
|
207
210
|
if (token !== undefined) {
|
|
208
211
|
if (await isListenerToken(token))
|
|
209
212
|
return {
|
|
210
|
-
connection: connectionFor(flight, TOKEN,
|
|
211
|
-
authority:
|
|
213
|
+
connection: connectionFor(flight, TOKEN, listenerAuthority, token),
|
|
214
|
+
authority: listenerAuthority,
|
|
212
215
|
};
|
|
213
216
|
const remembered = await findDevice(token);
|
|
214
217
|
|
package/src/local/provider.ts
CHANGED
|
@@ -18,7 +18,7 @@ const PLUGIN_FILE = /\.(?:ts|mts|js|mjs)$/;
|
|
|
18
18
|
|
|
19
19
|
const ROOT_SOURCES = [".nyte/SYSTEM.md", ".nyte/APPEND_SYSTEM.md", ".nyte/mcp.json"];
|
|
20
20
|
|
|
21
|
-
/** Returns the local environment provider (`kind: "local"`): a folder on this machine. `inspect` answers a `{ kind: "local", path }` target's `folderIdentity` (§41d), `available` when the path is a readable directory (else `unavailable { unreachable }`; a target without a path `unavailable { unsupported }`, identified by the hash of its locator), and the project sources present under it, relative to it, in this order: the plugin files `.nyte/plugins/<name>.{ts,mts,js,mjs}` and `.nyte/plugins/<name>/index.<ext>`, the skills `.nyte/skills/<name>/SKILL.md` and `.agents/skills/<name>/SKILL.md`, the prompts `.nyte/prompts/<name>.md`, then `.nyte/SYSTEM.md`, `.nyte/APPEND_SYSTEM.md` and `.nyte/mcp.json`: the files project trust gates. Context files such as `AGENTS.md` load without trust and are not listed. `attach` resolves the real path (a target that is not a directory throws `SdkError("unavailable")`) and returns an `ExecutionEnv` with that identity, `cwd` the real path, `files` from `localFiles` (with `ripgrep` and `formatter`), `exec` from `localExec`, and a `close` that ends its file watches and the jobs' readers, leaving running jobs alone. `git` is `localGit` (with `git`), `github` is `localGitHub` (with `gh` and `git`; the login is the machine's, `localGitHubAccount`), `snapshots` is `localSnapshots` storing tree walks under `<home>/.nyte/snapshots` (`home` default `os.homedir()`), and absent for a folder that contains `<home>/.nyte` or lies inside it outside `<home>/.nyte/worktrees` (the home folder itself, its ancestors, Nyte's own state): there a snapshot would walk or stage everything under home, the store and secrets included, to report changes in the store it just wrote; `worktrees` is `localWorktrees` (with `home` and `git`); no `browser` (the desktop wraps one in); `destroy` rejects with `SdkError("unavailable")`: a local folder is never torn down. */
|
|
21
|
+
/** Returns the local environment provider (`kind: "local"`): a folder on this machine. `inspect` answers a `{ kind: "local", path }` target's `folderIdentity` (§41d), `available` when the path is a readable directory (else `unavailable { unreachable }`; a target without a path `unavailable { unsupported }`, identified by the hash of its locator), and the project sources present under it, relative to it, in this order: the plugin files `.nyte/plugins/<name>.{ts,mts,js,mjs}` and `.nyte/plugins/<name>/index.<ext>`, the skills `.nyte/skills/<name>/SKILL.md` and `.agents/skills/<name>/SKILL.md`, the prompts `.nyte/prompts/<name>.md`, then `.nyte/SYSTEM.md`, `.nyte/APPEND_SYSTEM.md` and `.nyte/mcp.json`: the files project trust gates. Context files such as `AGENTS.md` load without trust and are not listed. `attach` resolves the real path (a target that is not a directory throws `SdkError("unavailable")`) and returns an `ExecutionEnv` with that identity, `cwd` the real path, `files` from `localFiles` (with `ripgrep` and `formatter`), `exec` from `localExec`, and a `close` that ends its file watches and the jobs' readers, leaving running jobs alone. `git` is `localGit` (with `git`), `github` is `localGitHub` (with `gh` and `git`; the login is the machine's, `localGitHubAccount`), `snapshots` is `localSnapshots` storing tree walks under `<home>/.nyte/snapshots` (`home` default `os.homedir()`), and absent for a folder that contains `<home>/.nyte` or lies inside it outside `<home>/.nyte/worktrees` (the home folder itself, its ancestors, Nyte's own state): there a snapshot would walk or stage everything under home, the store and secrets included, to report changes in the store it just wrote; `worktrees` is `localWorktrees` (with `home` and `git`); `realpath` is `fs.promises.realpath` (the native one, which spells each name as the disk stores it), a relative path resolved against `env.cwd`, null where it fails; no `browser` (the desktop wraps one in); `destroy` rejects with `SdkError("unavailable")`: a local folder is never torn down. */
|
|
22
22
|
export function localProvider(options?: {
|
|
23
23
|
readonly home?: string;
|
|
24
24
|
readonly git?: string;
|
|
@@ -95,6 +95,8 @@ export function localProvider(options?: {
|
|
|
95
95
|
: localSnapshots(env, { store, git: options?.git });
|
|
96
96
|
},
|
|
97
97
|
worktrees: localWorktrees({ home, git: options?.git }),
|
|
98
|
+
realpath: (env, path) =>
|
|
99
|
+
realpath(isAbsolute(path) ? path : join(env.cwd, path)).catch(() => null),
|
|
98
100
|
async destroy() {
|
|
99
101
|
throw new SdkError("unavailable", "A local folder is never torn down.");
|
|
100
102
|
},
|
package/src/local/worktrees.ts
CHANGED
|
@@ -1,15 +1,65 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { copyFile, mkdir, realpath, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { basename,
|
|
4
|
+
import { basename, join, resolve } from "node:path";
|
|
5
5
|
import { SdkError } from "@nyte-ai/schema";
|
|
6
6
|
import type { WorkspaceTarget } from "@nyte-ai/schema";
|
|
7
7
|
import type { WorktreeProvider } from "@nyte-ai/core";
|
|
8
|
-
import { sha256 } from "../hash.ts";
|
|
9
8
|
import { run } from "./command.ts";
|
|
10
9
|
|
|
11
10
|
const ENV: Readonly<Record<string, string>> = { GIT_TERMINAL_PROMPT: "0", LC_ALL: "C" };
|
|
12
11
|
|
|
12
|
+
/** The branch config key (`branch.<name>.nyte-session`) naming the session a worktree's branch was made for; `git branch -D` drops it with the branch. */
|
|
13
|
+
const SESSION_KEY = "nyte-session";
|
|
14
|
+
|
|
15
|
+
const STOPWORDS = new Set([
|
|
16
|
+
"a",
|
|
17
|
+
"an",
|
|
18
|
+
"the",
|
|
19
|
+
"and",
|
|
20
|
+
"or",
|
|
21
|
+
"of",
|
|
22
|
+
"to",
|
|
23
|
+
"in",
|
|
24
|
+
"on",
|
|
25
|
+
"for",
|
|
26
|
+
"with",
|
|
27
|
+
"from",
|
|
28
|
+
"by",
|
|
29
|
+
"at",
|
|
30
|
+
"into",
|
|
31
|
+
"as",
|
|
32
|
+
"is",
|
|
33
|
+
"are",
|
|
34
|
+
"be",
|
|
35
|
+
"it",
|
|
36
|
+
"its",
|
|
37
|
+
"this",
|
|
38
|
+
"that",
|
|
39
|
+
"my",
|
|
40
|
+
"our",
|
|
41
|
+
"your",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const TYPE_WORDS = new Set([
|
|
45
|
+
"feat",
|
|
46
|
+
"feature",
|
|
47
|
+
"fix",
|
|
48
|
+
"add",
|
|
49
|
+
"chore",
|
|
50
|
+
"refactor",
|
|
51
|
+
"docs",
|
|
52
|
+
"test",
|
|
53
|
+
"perf",
|
|
54
|
+
"build",
|
|
55
|
+
"ci",
|
|
56
|
+
"revert",
|
|
57
|
+
"update",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
/** How many names `create` tries before giving up: `<words>`, then `<two words>-2` up to this. */
|
|
61
|
+
const MAX_ATTEMPTS = 100;
|
|
62
|
+
|
|
13
63
|
/** One checkout of a repository: its folder and the branch it has out, null when HEAD is detached. */
|
|
14
64
|
export interface ListedWorktree {
|
|
15
65
|
readonly path: string;
|
|
@@ -81,11 +131,15 @@ export async function hasBranch(
|
|
|
81
131
|
|
|
82
132
|
/**
|
|
83
133
|
* Returns git worktrees for local workspaces (`git`, default `git` on the
|
|
84
|
-
* PATH), each under `<home>/.nyte/worktrees/<repository>/<
|
|
85
|
-
* `
|
|
134
|
+
* PATH), each under `<home>/.nyte/worktrees/<repository>/<branch>` on a branch
|
|
135
|
+
* named by `branchName`: at most three hyphenated words from the title.
|
|
86
136
|
*
|
|
87
137
|
* `create` refuses a folder outside a repository or one without a commit, then
|
|
88
|
-
* answers the worktree
|
|
138
|
+
* answers the worktree on the branch whose `branch.<branch>.nyte-session`
|
|
139
|
+
* config is `name`, or claims the first name with no folder or listed worktree
|
|
140
|
+
* at its path by creating its branch at HEAD (git refuses an existing branch,
|
|
141
|
+
* which also settles a race with a concurrent create: the loser tries the next
|
|
142
|
+
* name), records `name` in that config, and adds a worktree for it; then it
|
|
89
143
|
* copies the workspace's uncommitted changes in: the worktree's tracked and
|
|
90
144
|
* untracked files, ignored ones aside, are written to a tree through a
|
|
91
145
|
* temporary index seeded from the real one (which is never touched), and the
|
|
@@ -126,6 +180,77 @@ export function localWorktrees(options?: {
|
|
|
126
180
|
}
|
|
127
181
|
};
|
|
128
182
|
|
|
183
|
+
const findClaimedBranch = async (root: string, name: string): Promise<string | null> => {
|
|
184
|
+
const out = await git(root, [
|
|
185
|
+
"config",
|
|
186
|
+
"-z",
|
|
187
|
+
"--get-regexp",
|
|
188
|
+
`^branch\\..*\\.${SESSION_KEY}$`,
|
|
189
|
+
]).catch(() => "");
|
|
190
|
+
|
|
191
|
+
for (const entry of out.split("\0")) {
|
|
192
|
+
const newline = entry.indexOf("\n");
|
|
193
|
+
const branch = entry.slice("branch.".length, newline - SESSION_KEY.length - 1);
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
newline > 0 &&
|
|
197
|
+
entry.slice(newline + 1) === name &&
|
|
198
|
+
(await hasBranch(root, branch, options))
|
|
199
|
+
)
|
|
200
|
+
return branch;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return null;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const claimBranch = async (
|
|
207
|
+
root: string,
|
|
208
|
+
parent: string,
|
|
209
|
+
listed: readonly ListedWorktree[],
|
|
210
|
+
input: { readonly name: string; readonly title: string | null },
|
|
211
|
+
head: string,
|
|
212
|
+
): Promise<string> => {
|
|
213
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
214
|
+
const branch = branchName(input.title, attempt);
|
|
215
|
+
const folder = join(parent, branch);
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
listed.some((worktree) => worktree.path === folder) ||
|
|
219
|
+
(await stat(folder).catch(() => null)) !== null
|
|
220
|
+
)
|
|
221
|
+
continue;
|
|
222
|
+
|
|
223
|
+
const [created] = await Promise.allSettled([
|
|
224
|
+
git(root, ["branch", "--no-track", branch, head]),
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
if (created.status === "rejected") {
|
|
228
|
+
if (await hasBranch(root, branch, options)) continue;
|
|
229
|
+
throw created.reason;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await writeConfig(root, `branch.${branch}.${SESSION_KEY}`, input.name);
|
|
233
|
+
|
|
234
|
+
return branch;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
throw new SdkError(
|
|
238
|
+
"unavailable",
|
|
239
|
+
`Every worktree name for "${input.title ?? input.name}" is taken in ${root}.`,
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const writeConfig = async (root: string, key: string, value: string): Promise<void> => {
|
|
244
|
+
for (let attempt = 1; ; attempt++) {
|
|
245
|
+
const [written] = await Promise.allSettled([git(root, ["config", key, value])]);
|
|
246
|
+
|
|
247
|
+
if (written.status === "fulfilled") return;
|
|
248
|
+
|
|
249
|
+
if (attempt >= 20) throw written.reason;
|
|
250
|
+
await new Promise((settle) => setTimeout(settle, 10 * attempt));
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
129
254
|
return {
|
|
130
255
|
async create(env, input) {
|
|
131
256
|
const repo = await readRepository(git, env.cwd);
|
|
@@ -143,42 +268,41 @@ export function localWorktrees(options?: {
|
|
|
143
268
|
`${repo.root} has no commits yet, so a worktree has nothing to branch from.`,
|
|
144
269
|
);
|
|
145
270
|
|
|
146
|
-
const slug = worktreeSlug(input.name, input.title);
|
|
147
|
-
const branch = `nyte/${slug}`;
|
|
148
271
|
const below = (root: string): WorkspaceTarget => ({
|
|
149
272
|
kind: "local",
|
|
150
273
|
path: resolve(root, repo.prefix),
|
|
151
274
|
});
|
|
152
275
|
|
|
153
|
-
const listed =
|
|
154
|
-
|
|
155
|
-
);
|
|
276
|
+
const listed = await listWorktrees(repo.root, options);
|
|
277
|
+
const claimed = await findClaimedBranch(repo.root, input.name);
|
|
278
|
+
const open = listed.find((worktree) => claimed !== null && worktree.branch === claimed);
|
|
156
279
|
|
|
157
|
-
if (
|
|
280
|
+
if (claimed !== null && open !== undefined)
|
|
158
281
|
return {
|
|
159
|
-
target: below(
|
|
160
|
-
branch,
|
|
161
|
-
base: (await commitAt(repo.root, `refs/heads/${
|
|
282
|
+
target: below(open.path),
|
|
283
|
+
branch: claimed,
|
|
284
|
+
base: (await commitAt(repo.root, `refs/heads/${claimed}`)) ?? head,
|
|
162
285
|
};
|
|
163
286
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
const
|
|
287
|
+
const parent = join(folders, basename(repo.root));
|
|
288
|
+
await mkdir(parent, { recursive: true });
|
|
289
|
+
const realParent = await realpath(parent);
|
|
290
|
+
const branch = claimed ?? (await claimBranch(repo.root, realParent, listed, input, head));
|
|
291
|
+
|
|
292
|
+
const base =
|
|
293
|
+
claimed === null ? head : ((await commitAt(repo.root, `refs/heads/${claimed}`)) ?? head);
|
|
294
|
+
|
|
295
|
+
const folder = join(realParent, branch);
|
|
167
296
|
const tree = await workingTree(repo);
|
|
168
297
|
|
|
169
|
-
await
|
|
170
|
-
await addWorktree(
|
|
171
|
-
repo.root,
|
|
172
|
-
branched ? { path: folder, branch } : { path: folder, branch, from: head },
|
|
173
|
-
options,
|
|
174
|
-
);
|
|
298
|
+
await addWorktree(repo.root, { path: folder, branch }, options);
|
|
175
299
|
|
|
176
300
|
if (tree !== (await git(folder, ["rev-parse", "HEAD^{tree}"])).trim()) {
|
|
177
301
|
await git(folder, ["read-tree", "-m", "-u", "HEAD", tree]);
|
|
178
302
|
await git(folder, ["reset", "-q"]);
|
|
179
303
|
}
|
|
180
304
|
|
|
181
|
-
return { target: below(
|
|
305
|
+
return { target: below(folder), branch, base };
|
|
182
306
|
},
|
|
183
307
|
async remove(worktree) {
|
|
184
308
|
const path = "path" in worktree.target ? worktree.target.path : null;
|
|
@@ -227,13 +351,24 @@ async function readRepository(git: Git, cwd: string): Promise<Repository | null>
|
|
|
227
351
|
: { root: resolve(root), gitDir: resolve(gitDir), prefix };
|
|
228
352
|
}
|
|
229
353
|
|
|
230
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Names a worktree's branch from its title: lowercase ASCII words, stopwords
|
|
356
|
+
* dropped, and type words such as `fix` or `add` too while two words are left
|
|
357
|
+
* without them; three words on the first attempt, then two and `-<attempt>`,
|
|
358
|
+
* so a name never has more than three hyphenated parts. `worktree` stands in
|
|
359
|
+
* for a title with no words.
|
|
360
|
+
*/
|
|
361
|
+
function branchName(title: string | null, attempt: number): string {
|
|
231
362
|
const words = (title ?? "")
|
|
363
|
+
.normalize("NFKD")
|
|
232
364
|
.toLowerCase()
|
|
233
|
-
.
|
|
234
|
-
.
|
|
235
|
-
|
|
236
|
-
const
|
|
365
|
+
.split(/[^a-z0-9]+/u)
|
|
366
|
+
.filter((word) => word !== "");
|
|
367
|
+
|
|
368
|
+
const kept = words.filter((word) => !STOPWORDS.has(word));
|
|
369
|
+
const content = kept.filter((word) => !TYPE_WORDS.has(word));
|
|
370
|
+
const chosen = content.length >= 2 ? content : kept.length > 0 ? kept : words;
|
|
371
|
+
const stem = chosen.length === 0 ? "worktree" : chosen.slice(0, attempt === 1 ? 3 : 2).join("-");
|
|
237
372
|
|
|
238
|
-
return
|
|
373
|
+
return attempt === 1 ? stem : `${stem}-${attempt}`;
|
|
239
374
|
}
|
package/src/open.ts
CHANGED
|
@@ -50,6 +50,8 @@ import { defaultSecrets } from "./secrets.ts";
|
|
|
50
50
|
import type { SecretsSource } from "./secrets.ts";
|
|
51
51
|
import { sqliteStore, storePath } from "./sqlite.ts";
|
|
52
52
|
|
|
53
|
+
declare const NYTE_BUILT_VERSION: string | undefined;
|
|
54
|
+
|
|
53
55
|
const RELOAD_DEBOUNCE_MS = 250;
|
|
54
56
|
|
|
55
57
|
const STORE_POLL_MS = 500;
|
|
@@ -441,7 +443,11 @@ async function settingsSeed(home: string): Promise<{
|
|
|
441
443
|
}
|
|
442
444
|
|
|
443
445
|
function hostDescription(): HostDescription {
|
|
444
|
-
return {
|
|
446
|
+
return {
|
|
447
|
+
name: hostname(),
|
|
448
|
+
platform: process.platform,
|
|
449
|
+
version: typeof NYTE_BUILT_VERSION === "string" ? NYTE_BUILT_VERSION : manifest.version,
|
|
450
|
+
};
|
|
445
451
|
}
|
|
446
452
|
|
|
447
453
|
function settingsPath(home: string): string {
|
package/src/serve/binding.ts
CHANGED
|
@@ -103,7 +103,6 @@ const attempts = new WeakMap<ServerHost, Map<string, Attempt>>();
|
|
|
103
103
|
|
|
104
104
|
const holds = new WeakMap<ServerHost, Map<string, Page[]>>();
|
|
105
105
|
|
|
106
|
-
/** Returns each row bound to the call it names: ids bind handles on the connection, handles returned become ids, `Set`s and `Map`s become their wire forms; `head.move`, `files.batch` and `browser.open` go through the server port's wire operations with their wire id (§40o, §40x). `head.watch` sends the head's view from the host's fold whole, then each later view as its changes (`viewFrames`), the core watch marking the principal's seen tip (§40a, §54a); `sessions.watch` sends the list whole, then each later list as its order and changed rows (`sessionsFrames`). Authority is the caller's gate: the server checks each row's before binding. */
|
|
107
106
|
export function bindConnection(connection: Connection, options: BindingOptions): Binding {
|
|
108
107
|
const { wire } = options.server;
|
|
109
108
|
const { principal } = connection;
|
|
@@ -285,6 +284,11 @@ export function bindConnection(connection: Connection, options: BindingOptions):
|
|
|
285
284
|
|
|
286
285
|
return viewFrames(session(id).head(head).watch(options));
|
|
287
286
|
},
|
|
287
|
+
"head.see": async ({ session: id, head, commit }) => {
|
|
288
|
+
await session(id).head(head).see(commit);
|
|
289
|
+
|
|
290
|
+
return null;
|
|
291
|
+
},
|
|
288
292
|
"head.history": ({ session: id, head, before, limit }) =>
|
|
289
293
|
session(id).head(head).history({ before, limit }),
|
|
290
294
|
"head.tree": ({ session: id, head }) => session(id).head(head).tree(),
|
|
@@ -293,6 +297,14 @@ export function bindConnection(connection: Connection, options: BindingOptions):
|
|
|
293
297
|
}),
|
|
294
298
|
"head.compact": ({ session: id, head, instructions }) =>
|
|
295
299
|
session(id).head(head).compact({ instructions }),
|
|
300
|
+
"head.revise": ({ session: id, head, message, content, options }) =>
|
|
301
|
+
session(id).head(head).revise(message, content, options),
|
|
302
|
+
"head.hold": ({ session: id, head, message }) => wire.hold(id, head, message),
|
|
303
|
+
"head.release": async ({ session: id, head, message }) => {
|
|
304
|
+
await wire.release(id, head, message);
|
|
305
|
+
|
|
306
|
+
return null;
|
|
307
|
+
},
|
|
296
308
|
|
|
297
309
|
"heads.create": ({ session: id, name, input }) => session(id).heads.create(name, input),
|
|
298
310
|
"heads.merge": ({ session: id, name, options }) => session(id).heads.merge(name, options),
|
|
@@ -347,6 +359,11 @@ export function bindConnection(connection: Connection, options: BindingOptions):
|
|
|
347
359
|
? null
|
|
348
360
|
: { action: String(answer.action), input: toJson(answer.input) };
|
|
349
361
|
},
|
|
362
|
+
"part.watch": async ({ session: id, part }) => {
|
|
363
|
+
await session(id).info();
|
|
364
|
+
|
|
365
|
+
return session(id).part(part).watch();
|
|
366
|
+
},
|
|
350
367
|
"part.close": async ({ session: id, part }) => {
|
|
351
368
|
await session(id).part(part).close();
|
|
352
369
|
|
package/src/usage-history.ts
CHANGED
|
@@ -159,7 +159,6 @@ async function* historyLines(path: string, start: number, signal: AbortSignal |
|
|
|
159
159
|
const chunk = pending.length === 0 ? read : Buffer.concat([pending, read]);
|
|
160
160
|
let from = 0;
|
|
161
161
|
|
|
162
|
-
// oxlint-disable-next-line nyte-code/no-index-of -- a byte search keeps offsets exact, which decoded text cannot
|
|
163
162
|
for (let newline = chunk.indexOf(0x0a); newline !== -1; newline = chunk.indexOf(0x0a, from)) {
|
|
164
163
|
yield {
|
|
165
164
|
text: chunk.toString("utf8", from, newline),
|