heyquilt 0.3.122 → 0.3.138
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/RELEASES.md +273 -17
- package/bin/quilt.js +55 -9
- package/desktop/main.js +16 -2
- package/package.json +3 -2
- package/src/agent-task-workflow.js +2 -2
- package/src/agents/common.js +13 -0
- package/src/ai-vm.js +9 -1
- package/src/api/activity.js +5 -0
- package/src/api/memory-store.js +16 -9
- package/src/api/notify.js +134 -0
- package/src/api/routes/browsers.js +29 -0
- package/src/api/routes/github.js +0 -13
- package/src/api/routes/relay.js +26 -3
- package/src/api/routes/session-migrate.js +7 -4
- package/src/api/routes/sessions.js +73 -1
- package/src/api/server.js +13 -7
- package/src/api/supabase-store.js +43 -4
- package/src/autowork.js +182 -16
- package/src/connection.js +36 -9
- package/src/control.js +129 -15
- package/src/force-date.js +27 -0
- package/src/fsutil.js +13 -3
- package/src/gitstate.js +38 -2
- package/src/history.js +13 -3
- package/src/mcp.js +87 -12
- package/src/merge3.js +11 -3
- package/src/now-summary.js +113 -0
- package/src/pathrules.js +12 -0
- package/src/protocol.js +10 -2
- package/src/quilt-default.js +84 -0
- package/src/quilt-history.js +106 -0
- package/src/quilt-remote.js +78 -0
- package/src/relay-commit.js +5 -0
- package/src/relay-lease.js +6 -2
- package/src/relay-mcp.js +151 -17
- package/src/relay-outbound.js +1 -1
- package/src/release-words.js +2 -2
- package/src/runner.js +20 -7
- package/src/seam-access.js +97 -0
- package/src/seam-claims.js +53 -0
- package/src/seam-url.js +15 -0
- package/src/server.js +1517 -86
- package/src/session-follow.js +461 -27
- package/src/session-layout3.js +410 -4
- package/src/session-migrate.js +15 -11
- package/src/session-quilt.js +1 -1
- package/src/session-seam.js +233 -20
- package/src/session.js +848 -67
- package/src/source-live/bring-in.js +283 -0
- package/src/source-live/live-block.js +504 -33
- package/src/source-live/live-square.js +16 -5
- package/src/status.js +5 -0
- package/src/ui/agent-guide.js +1 -1
- package/src/ui/app.css +85 -20
- package/src/ui/app.js +4 -1
- package/src/ui/chat.js +9 -3
- package/src/ui/cloud-ai-model.js +13 -0
- package/src/ui/cloud-ai.js +10 -2
- package/src/ui/common.js +6 -2
- package/src/ui/fileview.js +20 -7
- package/src/ui/force-words.js +19 -0
- package/src/ui/git-rule.js +9 -0
- package/src/ui/home.js +0 -147
- package/src/ui/loom-model.js +15 -1
- package/src/ui/quiltview.js +213 -0
- package/src/ui/seam-model.js +251 -0
- package/src/ui/session.js +413 -31
- package/src/ui/voice-call.js +83 -14
- package/src/ui/voice-model.js +39 -0
- package/src/ui/voice.js +167 -17
- package/src/ui-server.js +80 -28
- package/src/update-check.js +25 -1
- package/src/voice.js +13 -6
- package/src/git.js +0 -147
- package/src/quilts/AGENTS.md +0 -1
- package/src/quilts/CLAUDE.md +0 -1
- package/src/quilts/accounts.js +0 -136
- package/src/quilts/affected.js +0 -132
- package/src/quilts/archiver.js +0 -175
- package/src/quilts/autorevert.js +0 -49
- package/src/quilts/bisect-run.js +0 -31
- package/src/quilts/blame.js +0 -103
- package/src/quilts/block.js +0 -347
- package/src/quilts/bucket.js +0 -25
- package/src/quilts/cbor.js +0 -92
- package/src/quilts/checks.js +0 -308
- package/src/quilts/claims.js +0 -144
- package/src/quilts/cli.js +0 -536
- package/src/quilts/client.js +0 -137
- package/src/quilts/conflict-format.js +0 -190
- package/src/quilts/describe.js +0 -296
- package/src/quilts/dogfood.js +0 -82
- package/src/quilts/errors.js +0 -8
- package/src/quilts/events.js +0 -171
- package/src/quilts/export-git.js +0 -130
- package/src/quilts/export-remote.js +0 -156
- package/src/quilts/export.js +0 -81
- package/src/quilts/fastmerge.js +0 -41
- package/src/quilts/hash.js +0 -44
- package/src/quilts/history.js +0 -266
- package/src/quilts/ids.js +0 -12
- package/src/quilts/import-follow.js +0 -236
- package/src/quilts/import-git.js +0 -294
- package/src/quilts/import.js +0 -196
- package/src/quilts/index.js +0 -94
- package/src/quilts/keys.js +0 -186
- package/src/quilts/limits.js +0 -67
- package/src/quilts/lines.js +0 -49
- package/src/quilts/live.js +0 -50
- package/src/quilts/merge-smart.js +0 -114
- package/src/quilts/merge.js +0 -501
- package/src/quilts/oauth.js +0 -202
- package/src/quilts/objects.js +0 -144
- package/src/quilts/pack.js +0 -118
- package/src/quilts/records.js +0 -211
- package/src/quilts/resolutions.js +0 -69
- package/src/quilts/reviews.js +0 -208
- package/src/quilts/routes.js +0 -186
- package/src/quilts/runner.js +0 -95
- package/src/quilts/server.js +0 -144
- package/src/quilts/service.js +0 -963
- package/src/quilts/setup.js +0 -41
- package/src/quilts/signing.js +0 -149
- package/src/quilts/squares.js +0 -239
- package/src/quilts/store-memory.js +0 -844
- package/src/quilts/store-rpc.js +0 -90
- package/src/quilts/store-supabase.js +0 -13
- package/src/quilts/tags.js +0 -64
- package/src/quilts/textdiff.js +0 -96
- package/src/quilts/tokens.js +0 -66
- package/src/quilts/tools.js +0 -403
- package/src/quilts/tree.js +0 -351
- package/src/quilts/webhook.js +0 -116
- package/src/quilts/window.js +0 -121
- package/src/quilts/xdiff.js +0 -435
- package/src/ui/branches.js +0 -111
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Who each AI in a session that is a quilt is to Seam, with nothing set up by hand (Seam's
|
|
2
|
+
// plans/quilt-source-control/11.5). Seam takes Quilt's sign-ins through Quilt's identity service
|
|
3
|
+
// (GET /v1/seam/me on the accounts API): a person's Quilt sign-in is that person
|
|
4
|
+
// (person:<user id>), and a person's AI sessions on their computer are the person; an agent's
|
|
5
|
+
// Quilt key or pass (a cloud AI, a hosted agent) is that agent (agent:<id>), working for its
|
|
6
|
+
// person. SEAM_TOKEN, when set, is used as it is.
|
|
7
|
+
//
|
|
8
|
+
// An agent's own key only reads on Seam: it writes with an agent token. This trades the key for
|
|
9
|
+
// one per quilt, for the squares it works in (merge when its person holds merge, else write),
|
|
10
|
+
// and keeps it until a quarter of an hour before it ends: never once per call, which would
|
|
11
|
+
// reach Seam's 20 live tokens per agent. A token for other squares is revoked when a new one is made.
|
|
12
|
+
import { quiltClient } from '@heyquilt/seam/lib/quilts/client.js'
|
|
13
|
+
|
|
14
|
+
export const RENEW_MS = 15 * 60_000
|
|
15
|
+
|
|
16
|
+
/** Whether a credential is an agent's own (a Quilt agent key or pass, or a Seam agent's key from `seam agent new`), which trades for an agent token. */
|
|
17
|
+
export const isAgentKey = (token) => /^(?:QuiltPass |qa_|sak_)/.test(String(token || ''))
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Seam clients for one member, on Seam at `api` (a URL, or a function giving it at call time).
|
|
21
|
+
* `credential()` resolves to the token they sign in to Seam with
|
|
22
|
+
* (or null). `client(quilt, squares)` is a client acting as them in `quilt`; for an agent, with
|
|
23
|
+
* a token that reaches `squares` (ids; none: the squares it holds); one that only reads (`read`)
|
|
24
|
+
* takes the token it holds there already, whatever its squares. Its `token` is the one it uses.
|
|
25
|
+
*/
|
|
26
|
+
export function seamAccess ({ api, credential, make = quiltClient, now = Date.now, fetch: fetchImpl = globalThis.fetch }) {
|
|
27
|
+
const minted = new Map() // quilt -> { token, id, expires_at, max_level, squares }
|
|
28
|
+
const where = () => typeof api === 'function' ? api() : api
|
|
29
|
+
// A trade refused 404: Seam finds no quilt this agent reaches through the person it works for (an
|
|
30
|
+
// agent needs no member row of its own: Seam takes the person's). So the person is not in the quilt,
|
|
31
|
+
// or the quilt is gone: the agent is told who that person is to Seam, to pass on.
|
|
32
|
+
async function noQuilt (quilt, key) {
|
|
33
|
+
let person = ''
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetchImpl(`${String(where()).replace(/\/+$/, '')}/v1/me`, { headers: { authorization: /^QuiltPass /.test(key) ? key : `Bearer ${key}` }, signal: AbortSignal.timeout(10_000) })
|
|
36
|
+
const me = res.ok ? await res.json() : null
|
|
37
|
+
if (me && typeof me.on_behalf_of === 'string') person = me.on_behalf_of
|
|
38
|
+
} catch {}
|
|
39
|
+
const who = person || 'the person you work for'
|
|
40
|
+
return `Seam finds no quilt ${quilt} that the person this agent works for (${who}) is in: they are not a member of it, or it was deleted. Tell them: a quilt admin adds them once (seam quilts add ${person || 'person:<their id>'} --level write --quilt ${quilt}), and this agent then works there for them.`
|
|
41
|
+
}
|
|
42
|
+
async function mint (quilt, key, squares) {
|
|
43
|
+
const own = make({ api: where(), token: key })
|
|
44
|
+
const was = minted.get(quilt)
|
|
45
|
+
const body = { squares: squares && squares.length ? squares : 'own' }
|
|
46
|
+
let t
|
|
47
|
+
try { t = await own.createAgentToken(quilt, { ...body, max_level: 'merge' }) } catch (err) {
|
|
48
|
+
if (err.status === 404) throw Object.assign(new Error(await noQuilt(quilt, key)), { status: 404, code: 'no_quilt' })
|
|
49
|
+
// Its person holds write, not merge: a token that writes, and merges wait for a person.
|
|
50
|
+
if (err.status !== 403) throw err
|
|
51
|
+
t = await own.createAgentToken(quilt, { ...body, max_level: 'write' })
|
|
52
|
+
}
|
|
53
|
+
const kept = { token: t.token, id: t.id, expires_at: Number(t.expires_at) || now() + 8 * 3600e3, max_level: t.max_level || 'write', squares: body.squares }
|
|
54
|
+
minted.set(quilt, kept)
|
|
55
|
+
if (was && was.id && was.id !== kept.id && own.revokeAgentToken) own.revokeAgentToken(quilt, was.id).catch(() => {})
|
|
56
|
+
return kept
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
async client (quilt, squares = [], { read = false, retry } = {}) {
|
|
60
|
+
const at = where()
|
|
61
|
+
if (!at) throw new Error('Seam is not on for this app (QUILT_SOURCE_URL is not set).')
|
|
62
|
+
const key = await credential()
|
|
63
|
+
if (!key) throw new Error('This session has no Quilt sign-in to reach Seam with: sign in to Quilt (quilt login) and join again.')
|
|
64
|
+
// `retry: false`: a read that has somewhere else to go (quilt_history: the relay) is not held up by a Seam that is down.
|
|
65
|
+
const made = (token) => make({ api: at, token, ...(retry === false ? { retry: false } : {}) })
|
|
66
|
+
if (!isAgentKey(key) || !quilt) return { client: made(key), token: key, person: !isAgentKey(key) }
|
|
67
|
+
// An agent's own key does not even read a quilt it is not a member of (it reaches it through
|
|
68
|
+
// its person only with a token), so reading uses whichever token it holds there now.
|
|
69
|
+
const want = squares && squares.length ? squares : 'own'
|
|
70
|
+
let t = minted.get(quilt)
|
|
71
|
+
const fresh = t && t.expires_at - now() >= RENEW_MS
|
|
72
|
+
if (!fresh || (!read && JSON.stringify(t.squares) !== JSON.stringify(want))) t = await mint(quilt, key, squares)
|
|
73
|
+
return { client: made(t.token), token: t.token, level: t.max_level, person: false }
|
|
74
|
+
},
|
|
75
|
+
/** Forgets the tokens it made (a refused one is made again on the next call). */
|
|
76
|
+
forget () { minted.clear() }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The credential a folder's session reaches Seam with: SEAM_TOKEN, else its Quilt sign-in (`apiAuth`: the person's, or its agent's key). */
|
|
81
|
+
export function sessionCredential ({ env = process.env, apiAuth = null, account = () => null } = {}) {
|
|
82
|
+
return async () => {
|
|
83
|
+
if (env.SEAM_TOKEN) return env.SEAM_TOKEN
|
|
84
|
+
if (apiAuth) { try { const a = await apiAuth(); if (a && a.token) return a.token } catch {} }
|
|
85
|
+
const acc = account()
|
|
86
|
+
return (acc && acc.token) || null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Who `token` is to Seam at `api` (GET /v1/me): { member_id, kind, on_behalf_of? }, or null when Seam can't say. */
|
|
91
|
+
export async function seamWhoAmI (api, token, { fetch: fetchImpl = globalThis.fetch } = {}) {
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetchImpl(`${String(api).replace(/\/+$/, '')}/v1/me`, { headers: { authorization: /^QuiltPass /.test(token) ? token : `Bearer ${token}` }, signal: AbortSignal.timeout(10_000) })
|
|
94
|
+
const me = res.ok ? await res.json() : null
|
|
95
|
+
return me && typeof me.member_id === 'string' ? me : null
|
|
96
|
+
} catch { return null }
|
|
97
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Letting go of claims in a session that is a quilt (layout 3): its claims are Seam's, and each
|
|
2
|
+
// member lets go of theirs in Seam as themselves (Session.releaseInSeam). The move into Seam made
|
|
3
|
+
// the session's claims as the member who moved it (Seam makes a claim only as its caller), so the
|
|
4
|
+
// relay keeps whose each was (`moved`, server.js quilt-claims): those are their claimant's to let
|
|
5
|
+
// go of, not the mover's, and the session owner may let go of any one named.
|
|
6
|
+
import { seamWhoAmI } from './seam-access.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Which of Seam's `claims` this member lets go of for `pattern` ('*': all of theirs): { release,
|
|
10
|
+
* notYours: [{ pattern, holder }] }. `me`: the member's Seam id; `moved`: the relay's records of the
|
|
11
|
+
* move's claims ({ id, by, mine }); `owner`: the session owner, who lets go of any claim it names.
|
|
12
|
+
*/
|
|
13
|
+
export function claimsToRelease ({ claims = [], me = '', moved = [], pattern = '*', owner = false }) {
|
|
14
|
+
const all = !pattern || pattern === '*'
|
|
15
|
+
const rec = new Map((moved || []).map((m) => [m.id, m]))
|
|
16
|
+
const release = []
|
|
17
|
+
const notYours = []
|
|
18
|
+
for (const c of claims || []) {
|
|
19
|
+
if (!c || c.conflict || (!all && c.pattern !== pattern)) continue
|
|
20
|
+
const m = rec.get(c.id)
|
|
21
|
+
const theirs = m ? !!m.mine : !!me && c.holder === me
|
|
22
|
+
if (theirs || (owner && !all)) release.push(c)
|
|
23
|
+
else if (!all) notYours.push({ pattern: c.pattern, holder: m ? `${m.by || 'someone'} (moved into Seam with the session)` : (c.holder_name || c.holder) })
|
|
24
|
+
}
|
|
25
|
+
return { release, notYours }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lets go of this member's claims for `pattern` in Seam: `access` is the member's seamAccess (a
|
|
30
|
+
* person's sign-in, or an agent's key traded for a token that reaches the claims' squares); `asked`
|
|
31
|
+
* the relay's quilt-claims answer ({ moved, owner }). Resolves to { released, seam: true, held?,
|
|
32
|
+
* gone: [claim ids let go of] }; throws naming the holder when the one named is someone else's.
|
|
33
|
+
*/
|
|
34
|
+
export async function releaseInSeam ({ access, api, quilt, asked = {}, pattern = '*' }) {
|
|
35
|
+
const { client: reader, token } = await access.client(quilt, [], { read: true })
|
|
36
|
+
const me = await seamWhoAmI(api, token)
|
|
37
|
+
const got = await reader.claims(quilt)
|
|
38
|
+
const claims = Array.isArray(got) ? got : (got && got.claims) || []
|
|
39
|
+
const pick = claimsToRelease({ claims, me: (me && me.member_id) || '', moved: asked.moved, owner: !!asked.owner, pattern })
|
|
40
|
+
if (!pick.release.length && pick.notYours.length) throw new Error(`${pattern} is claimed by ${pick.notYours[0].holder}; only they can release it (or the session owner)`)
|
|
41
|
+
if (!pick.release.length) return { released: 0, seam: true, gone: [] }
|
|
42
|
+
const { client } = await access.client(quilt, [...new Set(pick.release.map((c) => c.square_id))])
|
|
43
|
+
const gone = []
|
|
44
|
+
const held = []
|
|
45
|
+
for (const c of pick.release) {
|
|
46
|
+
try { await client.unclaim(quilt, c.square_id, c.id); gone.push(c.id) } catch (err) {
|
|
47
|
+
if (err.code === 'claim_queued') { held.push(c.pattern); continue }
|
|
48
|
+
if (err.status === 404) { gone.push(c.id); continue } // let go of already
|
|
49
|
+
throw err
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { released: gone.length, seam: true, gone, ...(held.length ? { held } : {}) }
|
|
53
|
+
}
|
package/src/seam-url.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Where the app and the CLI find Seam, Quilt's source control (Seam's plans/quilt-source-control/16).
|
|
2
|
+
// On by default: QUILT_SOURCE_URL names another Seam, and `off` (or an empty value) turns it off.
|
|
3
|
+
// Only the app's side reads this. The relay and the API read QUILT_SOURCE_URL themselves and stay
|
|
4
|
+
// off without it. It changes nothing for a session the relay does not say is linked to a quilt.
|
|
5
|
+
|
|
6
|
+
export const SEAM_URL = 'https://api.seam.heyquilt.com'
|
|
7
|
+
|
|
8
|
+
/** The app's Seam API, or '' when it is turned off. */
|
|
9
|
+
export function appSeamUrl (env = process.env) {
|
|
10
|
+
const v = env ? env.QUILT_SOURCE_URL : undefined
|
|
11
|
+
if (v === undefined || v === null) return SEAM_URL
|
|
12
|
+
const t = String(v).trim()
|
|
13
|
+
if (!t || /^(off|0|false|no|none)$/i.test(t)) return ''
|
|
14
|
+
return t.replace(/\/+$/, '')
|
|
15
|
+
}
|