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.
Files changed (136) hide show
  1. package/RELEASES.md +273 -17
  2. package/bin/quilt.js +55 -9
  3. package/desktop/main.js +16 -2
  4. package/package.json +3 -2
  5. package/src/agent-task-workflow.js +2 -2
  6. package/src/agents/common.js +13 -0
  7. package/src/ai-vm.js +9 -1
  8. package/src/api/activity.js +5 -0
  9. package/src/api/memory-store.js +16 -9
  10. package/src/api/notify.js +134 -0
  11. package/src/api/routes/browsers.js +29 -0
  12. package/src/api/routes/github.js +0 -13
  13. package/src/api/routes/relay.js +26 -3
  14. package/src/api/routes/session-migrate.js +7 -4
  15. package/src/api/routes/sessions.js +73 -1
  16. package/src/api/server.js +13 -7
  17. package/src/api/supabase-store.js +43 -4
  18. package/src/autowork.js +182 -16
  19. package/src/connection.js +36 -9
  20. package/src/control.js +129 -15
  21. package/src/force-date.js +27 -0
  22. package/src/fsutil.js +13 -3
  23. package/src/gitstate.js +38 -2
  24. package/src/history.js +13 -3
  25. package/src/mcp.js +87 -12
  26. package/src/merge3.js +11 -3
  27. package/src/now-summary.js +113 -0
  28. package/src/pathrules.js +12 -0
  29. package/src/protocol.js +10 -2
  30. package/src/quilt-default.js +84 -0
  31. package/src/quilt-history.js +106 -0
  32. package/src/quilt-remote.js +78 -0
  33. package/src/relay-commit.js +5 -0
  34. package/src/relay-lease.js +6 -2
  35. package/src/relay-mcp.js +151 -17
  36. package/src/relay-outbound.js +1 -1
  37. package/src/release-words.js +2 -2
  38. package/src/runner.js +20 -7
  39. package/src/seam-access.js +97 -0
  40. package/src/seam-claims.js +53 -0
  41. package/src/seam-url.js +15 -0
  42. package/src/server.js +1517 -86
  43. package/src/session-follow.js +461 -27
  44. package/src/session-layout3.js +410 -4
  45. package/src/session-migrate.js +15 -11
  46. package/src/session-quilt.js +1 -1
  47. package/src/session-seam.js +233 -20
  48. package/src/session.js +848 -67
  49. package/src/source-live/bring-in.js +283 -0
  50. package/src/source-live/live-block.js +504 -33
  51. package/src/source-live/live-square.js +16 -5
  52. package/src/status.js +5 -0
  53. package/src/ui/agent-guide.js +1 -1
  54. package/src/ui/app.css +85 -20
  55. package/src/ui/app.js +4 -1
  56. package/src/ui/chat.js +9 -3
  57. package/src/ui/cloud-ai-model.js +13 -0
  58. package/src/ui/cloud-ai.js +10 -2
  59. package/src/ui/common.js +6 -2
  60. package/src/ui/fileview.js +20 -7
  61. package/src/ui/force-words.js +19 -0
  62. package/src/ui/git-rule.js +9 -0
  63. package/src/ui/home.js +0 -147
  64. package/src/ui/loom-model.js +15 -1
  65. package/src/ui/quiltview.js +213 -0
  66. package/src/ui/seam-model.js +251 -0
  67. package/src/ui/session.js +413 -31
  68. package/src/ui/voice-call.js +83 -14
  69. package/src/ui/voice-model.js +39 -0
  70. package/src/ui/voice.js +167 -17
  71. package/src/ui-server.js +80 -28
  72. package/src/update-check.js +25 -1
  73. package/src/voice.js +13 -6
  74. package/src/git.js +0 -147
  75. package/src/quilts/AGENTS.md +0 -1
  76. package/src/quilts/CLAUDE.md +0 -1
  77. package/src/quilts/accounts.js +0 -136
  78. package/src/quilts/affected.js +0 -132
  79. package/src/quilts/archiver.js +0 -175
  80. package/src/quilts/autorevert.js +0 -49
  81. package/src/quilts/bisect-run.js +0 -31
  82. package/src/quilts/blame.js +0 -103
  83. package/src/quilts/block.js +0 -347
  84. package/src/quilts/bucket.js +0 -25
  85. package/src/quilts/cbor.js +0 -92
  86. package/src/quilts/checks.js +0 -308
  87. package/src/quilts/claims.js +0 -144
  88. package/src/quilts/cli.js +0 -536
  89. package/src/quilts/client.js +0 -137
  90. package/src/quilts/conflict-format.js +0 -190
  91. package/src/quilts/describe.js +0 -296
  92. package/src/quilts/dogfood.js +0 -82
  93. package/src/quilts/errors.js +0 -8
  94. package/src/quilts/events.js +0 -171
  95. package/src/quilts/export-git.js +0 -130
  96. package/src/quilts/export-remote.js +0 -156
  97. package/src/quilts/export.js +0 -81
  98. package/src/quilts/fastmerge.js +0 -41
  99. package/src/quilts/hash.js +0 -44
  100. package/src/quilts/history.js +0 -266
  101. package/src/quilts/ids.js +0 -12
  102. package/src/quilts/import-follow.js +0 -236
  103. package/src/quilts/import-git.js +0 -294
  104. package/src/quilts/import.js +0 -196
  105. package/src/quilts/index.js +0 -94
  106. package/src/quilts/keys.js +0 -186
  107. package/src/quilts/limits.js +0 -67
  108. package/src/quilts/lines.js +0 -49
  109. package/src/quilts/live.js +0 -50
  110. package/src/quilts/merge-smart.js +0 -114
  111. package/src/quilts/merge.js +0 -501
  112. package/src/quilts/oauth.js +0 -202
  113. package/src/quilts/objects.js +0 -144
  114. package/src/quilts/pack.js +0 -118
  115. package/src/quilts/records.js +0 -211
  116. package/src/quilts/resolutions.js +0 -69
  117. package/src/quilts/reviews.js +0 -208
  118. package/src/quilts/routes.js +0 -186
  119. package/src/quilts/runner.js +0 -95
  120. package/src/quilts/server.js +0 -144
  121. package/src/quilts/service.js +0 -963
  122. package/src/quilts/setup.js +0 -41
  123. package/src/quilts/signing.js +0 -149
  124. package/src/quilts/squares.js +0 -239
  125. package/src/quilts/store-memory.js +0 -844
  126. package/src/quilts/store-rpc.js +0 -90
  127. package/src/quilts/store-supabase.js +0 -13
  128. package/src/quilts/tags.js +0 -64
  129. package/src/quilts/textdiff.js +0 -96
  130. package/src/quilts/tokens.js +0 -66
  131. package/src/quilts/tools.js +0 -403
  132. package/src/quilts/tree.js +0 -351
  133. package/src/quilts/webhook.js +0 -116
  134. package/src/quilts/window.js +0 -121
  135. package/src/quilts/xdiff.js +0 -435
  136. 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
+ }
@@ -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
+ }