@xingular/harness 1.5.0-unstable → 1.6.0-unstable

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 (95) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/browser-extension/README.md +32 -0
  3. package/browser-extension/background.js +612 -0
  4. package/browser-extension/manifest.json +16 -0
  5. package/browser-extension/overlay.js +97 -0
  6. package/browser-extension/popup.css +19 -0
  7. package/browser-extension/popup.html +30 -0
  8. package/browser-extension/popup.js +101 -0
  9. package/browser-extension/site-access.js +63 -0
  10. package/dist/cli.js +1489 -680
  11. package/dist/sdk/browser/config.d.ts +3 -0
  12. package/dist/sdk/browser/config.js +2 -1
  13. package/dist/sdk/browser/extension/bridge.d.ts +55 -0
  14. package/dist/sdk/browser/extension/bridge.js +336 -0
  15. package/dist/sdk/browser/extension/session.d.ts +50 -0
  16. package/dist/sdk/browser/extension/session.js +321 -0
  17. package/dist/sdk/browser/index.d.ts +7 -2
  18. package/dist/sdk/browser/index.js +58 -25
  19. package/dist/sdk/browser/serverMethods.js +4 -1
  20. package/dist/sdk/browser/tools.d.ts +99 -2
  21. package/dist/sdk/browser/tools.js +6 -1
  22. package/dist/sdk/commands/registry.d.ts +5 -0
  23. package/dist/sdk/commands/registry.js +1 -0
  24. package/dist/sdk/harness.d.ts +4 -0
  25. package/dist/sdk/harness.js +1 -1
  26. package/dist/sdk/pi/retryPolicy.d.ts +19 -0
  27. package/dist/sdk/pi/retryPolicy.js +11 -0
  28. package/dist/sdk/prompt/compile.d.ts +0 -2
  29. package/dist/sdk/prompt/compile.js +2 -6
  30. package/dist/sdk/prompt/reporting.d.ts +1 -1
  31. package/dist/sdk/prompt/reporting.js +30 -13
  32. package/dist/sdk/prompt/standard.d.ts +1 -1
  33. package/dist/sdk/prompt/standard.js +1 -1
  34. package/dist/sdk/remote/webClient.js +6 -2
  35. package/dist/sdk/sessions/contracts.d.ts +2 -0
  36. package/dist/sdk/sessions/contracts.js +5 -1
  37. package/dist/sdk/sessions/planMode.d.ts +16 -6
  38. package/dist/sdk/sessions/planMode.js +59 -23
  39. package/dist/sdk/sessions/runner.js +14 -5
  40. package/dist/sdk/sessions/workSlice.d.ts +15 -1
  41. package/dist/sdk/sessions/workSlice.js +17 -9
  42. package/dist/sdk/terminal/controller.d.ts +6 -0
  43. package/dist/sdk/terminal/controller.js +44 -5
  44. package/dist/sdk/terminal/opentui/model.js +3 -1
  45. package/dist/sdk/terminal/piExtension.js +7 -2
  46. package/dist/sdk/terminal/stallWatch.d.ts +18 -0
  47. package/dist/sdk/terminal/stallWatch.js +20 -0
  48. package/dist/sdk/terminal/subagentMonitor.d.ts +2 -0
  49. package/dist/sdk/terminal/subagentMonitor.js +4 -1
  50. package/dist/sdk/terminal/terminals.js +35 -4
  51. package/dist/sdk/terminal/toolName.d.ts +6 -0
  52. package/dist/sdk/terminal/toolName.js +12 -0
  53. package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
  54. package/dist/sdk/terminal/tui/commandPages.js +3 -0
  55. package/dist/sdk/terminal/tui/commands.d.ts +14 -0
  56. package/dist/sdk/terminal/tui/commands.js +4 -1
  57. package/dist/sdk/terminal/tui/pages/followUpQueuePage.d.ts +34 -0
  58. package/dist/sdk/terminal/tui/pages/followUpQueuePage.js +130 -0
  59. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +37 -7
  60. package/dist/sdk/terminal/tui/runtime.js +2 -1
  61. package/dist/sdk/terminal/tui/settingsModel.js +88 -7
  62. package/dist/sdk/tools/askUser.js +2 -2
  63. package/dist/sdk/world/world.d.ts +12 -2
  64. package/dist/sdk/world/world.js +42 -4
  65. package/dist/terminal-opentui.js +1456 -756
  66. package/dist/terminal-pi.js +62 -31
  67. package/docs/README.md +1 -0
  68. package/docs/architecture/02-sessions-and-runs.md +43 -25
  69. package/docs/architecture/04-prompt.md +23 -22
  70. package/docs/architecture/10-server-protocol.md +23 -3
  71. package/docs/architecture/11-terminal.md +41 -29
  72. package/docs/architecture/12-storage.md +2 -1
  73. package/docs/architecture/13-world-tools.md +35 -22
  74. package/docs/architecture/14-surfaces-and-parity.md +24 -20
  75. package/docs/architecture/README.md +4 -1
  76. package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +3 -1
  77. package/docs/decisions/ADR-0027-connected-sketch-display.md +4 -2
  78. package/docs/decisions/ADR-0076-codex-final-answer-verbatim.md +6 -1
  79. package/docs/decisions/ADR-0077-plan-runs-may-conclude-no-plan-needed.md +120 -0
  80. package/docs/decisions/ADR-0078-child-aware-stall-watch.md +46 -0
  81. package/docs/decisions/ADR-0079-phase-led-reporting-guidance.md +72 -0
  82. package/docs/decisions/ADR-0080-fixed-width-question-sketch-canvas.md +50 -0
  83. package/docs/decisions/ADR-0081-no-work-slice-budget-by-default.md +36 -0
  84. package/docs/decisions/ADR-0082-connected-brave-browser.md +72 -0
  85. package/docs/parity.md +7 -5
  86. package/docs/releases/1.5.0-unstable.md +12 -3
  87. package/docs/releases/1.5.1-unstable.md +24 -0
  88. package/docs/releases/1.6.0-unstable.md +59 -0
  89. package/docs/study/00-index.md +17 -1
  90. package/docs/study/STUDY-0007-claude-code-prompt.md +162 -0
  91. package/docs/study/STUDY-0008-browser-control.md +281 -0
  92. package/docs/study/STUDY-0009-claude-code-reporting.md +205 -0
  93. package/docs/study/sources/claude-code.md +51 -39
  94. package/docs/terminal-tui.md +46 -18
  95. package/package.json +2 -1
@@ -0,0 +1,97 @@
1
+ (() => {
2
+ const hostId = '__xingular_agent_browser_badge__'
3
+ let host = document.getElementById(hostId)
4
+ if (host) return
5
+
6
+ host = document.createElement('div')
7
+ host.id = hostId
8
+ host.setAttribute('role', 'group')
9
+ host.setAttribute('aria-label', 'Xingular Agent browser controls')
10
+ host.style.cssText = 'all:initial;position:fixed;inset:auto 16px 16px auto;z-index:2147483647;pointer-events:auto;'
11
+ const root = host.attachShadow({ mode: 'closed' })
12
+ const style = document.createElement('style')
13
+ style.textContent = `
14
+ :host { all: initial; }
15
+ .badge { display:flex; align-items:center; gap:9px; padding:8px 10px 8px 12px; border:1px solid rgba(255,255,255,.16); border-radius:999px; background:#17201e; color:#f5f4ee; box-shadow:0 8px 24px rgba(0,0,0,.28); font:500 12px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; letter-spacing:0; }
16
+ .dot { width:7px; height:7px; flex:none; border-radius:50%; background:#81d5a8; box-shadow:0 0 0 3px rgba(129,213,168,.15); }
17
+ .badge.paused .dot { background:#e5b960; box-shadow:0 0 0 3px rgba(229,185,96,.15); }
18
+ .label { white-space:nowrap; }
19
+ button { appearance:none; border:0; border-radius:999px; padding:5px 9px; background:#2b3834; color:inherit; font:inherit; cursor:pointer; }
20
+ button:hover { background:#3a4b45; }
21
+ button:focus-visible { outline:2px solid #c5ead7; outline-offset:2px; }
22
+ button.stop { background:#643d3a; }
23
+ button.stop:hover { background:#7a4945; }
24
+ .pulse { position:fixed; width:14px; height:14px; margin:-7px 0 0 -7px; border:2px solid #d8f7e5; border-radius:50%; background:rgba(94,193,139,.22); box-shadow:0 0 0 4px rgba(94,193,139,.2); pointer-events:none; animation:agent-pulse .65s cubic-bezier(.2,.8,.2,1) forwards; }
25
+ @keyframes agent-pulse { from { opacity:.95; transform:scale(.45); } to { opacity:0; transform:scale(2.7); } }
26
+ `
27
+ const badge = document.createElement('div')
28
+ badge.className = 'badge'
29
+ const dot = document.createElement('span')
30
+ dot.className = 'dot'
31
+ const label = document.createElement('span')
32
+ label.className = 'label'
33
+ label.textContent = 'Xingular Agent is on this site'
34
+ const pause = document.createElement('button')
35
+ pause.type = 'button'
36
+ pause.textContent = 'Pause'
37
+ pause.setAttribute('aria-label', 'Pause browser control')
38
+ const stop = document.createElement('button')
39
+ stop.type = 'button'
40
+ stop.className = 'stop'
41
+ stop.textContent = 'Stop'
42
+ stop.setAttribute('aria-label', 'Stop browser control')
43
+ badge.append(dot, label, pause, stop)
44
+ root.append(style, badge)
45
+ document.documentElement.append(host)
46
+
47
+ let paused = false
48
+ const refresh = () => {
49
+ badge.classList.toggle('paused', paused)
50
+ label.textContent = paused ? 'Xingular Agent is paused' : 'Xingular Agent is on this site'
51
+ pause.textContent = paused ? 'Resume' : 'Pause'
52
+ pause.setAttribute('aria-label', paused ? 'Resume browser control' : 'Pause browser control')
53
+ }
54
+
55
+ const ask = action => chrome.runtime.sendMessage({ type: 'control', action })
56
+ pause.addEventListener('click', async event => {
57
+ event.preventDefault()
58
+ event.stopPropagation()
59
+ const result = await ask(paused ? 'resume' : 'pause').catch(() => null)
60
+ if (result?.ok) {
61
+ paused = !paused
62
+ refresh()
63
+ }
64
+ })
65
+ stop.addEventListener('click', async event => {
66
+ event.preventDefault()
67
+ event.stopPropagation()
68
+ await ask('stop').catch(() => null)
69
+ })
70
+
71
+ let mutationTimer = null
72
+ const observer = new MutationObserver(records => {
73
+ if (records.every(record => host.contains(record.target))) return
74
+ if (mutationTimer) clearTimeout(mutationTimer)
75
+ mutationTimer = setTimeout(() => chrome.runtime.sendMessage({ type: 'page-changed' }).catch(() => {}), 250)
76
+ })
77
+ const onMessage = message => {
78
+ if (message?.type === 'badge') {
79
+ paused = message.paused === true
80
+ refresh()
81
+ } else if (message?.type === 'pulse') {
82
+ const pulse = document.createElement('span')
83
+ pulse.className = 'pulse'
84
+ pulse.style.left = `${Number(message.x) || 0}px`
85
+ pulse.style.top = `${Number(message.y) || 0}px`
86
+ root.append(pulse)
87
+ setTimeout(() => pulse.remove(), 750)
88
+ } else if (message?.type === 'clear') {
89
+ observer.disconnect()
90
+ if (mutationTimer) clearTimeout(mutationTimer)
91
+ chrome.runtime.onMessage.removeListener(onMessage)
92
+ host.remove()
93
+ }
94
+ }
95
+ chrome.runtime.onMessage.addListener(onMessage)
96
+ observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['aria-label', 'aria-expanded', 'disabled', 'href', 'role', 'value'] })
97
+ })()
@@ -0,0 +1,19 @@
1
+ * { box-sizing: border-box; }
2
+ body { width: 320px; margin: 0; background: #f4f3ec; color: #202823; font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
3
+ main { padding: 20px; }
4
+ h1 { margin: 0 0 8px; font-size: 19px; line-height: 1.2; letter-spacing: -.025em; }
5
+ h2 { margin: 0 0 4px; font-size: 14px; font-weight: 650; }
6
+ p { margin: 0 0 12px; color: #46534b; }
7
+ #status { min-height: 22px; color: #28633f; font-weight: 600; }
8
+ section { padding-top: 14px; border-top: 1px solid #d7d9ce; margin-top: 14px; }
9
+ label { display: block; margin-bottom: 5px; font-weight: 600; }
10
+ input { width: 100%; min-height: 38px; margin-bottom: 9px; border: 1px solid #a9b2a8; border-radius: 8px; padding: 8px 10px; color: #202823; background: #fff; font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; }
11
+ button { width: 100%; min-height: 38px; margin-top: 7px; border: 0; border-radius: 8px; padding: 8px 12px; background: #23583a; color: #fff; font: inherit; font-size: 13px; font-weight: 600; line-height: 1.2; cursor: pointer; }
12
+ button:hover { background: #19462d; }
13
+ button:focus-visible, input:focus-visible { outline: 3px solid #91c7a4; outline-offset: 2px; }
14
+ button:disabled { opacity: .55; cursor: default; }
15
+ button.secondary { background: transparent; color: #35473b; border: 1px solid #a9b2a8; }
16
+ button.secondary:hover { background: #e5e8df; }
17
+ .note { margin-top: 16px; font-size: 12px; line-height: 1.45; }
18
+ #error { margin-top: 12px; color: #962f2a; }
19
+ [hidden] { display: none !important; }
@@ -0,0 +1,30 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Xingular Agent</title>
7
+ <link rel="stylesheet" href="popup.css">
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>Brave browser access</h1>
12
+ <p id="status" role="status">Checking connection…</p>
13
+ <section id="pairing" hidden>
14
+ <label for="pairing-code">Pairing code</label>
15
+ <input id="pairing-code" autocomplete="off" spellcheck="false" placeholder="38671:ABCDEF012345">
16
+ <button id="connect" type="button">Connect to Harness</button>
17
+ </section>
18
+ <section id="site-access">
19
+ <h2 id="site-name">Current site</h2>
20
+ <p id="site-detail">Only the site you allow is exposed to Xingular Agent.</p>
21
+ <button id="allow" type="button">Allow this site</button>
22
+ <button id="remove" type="button" class="secondary" hidden>Remove site access</button>
23
+ </section>
24
+ <p class="note">Access covers ordinary tabs on this site. Incognito tabs are excluded. The extension does not read history or cookies.</p>
25
+ <p id="error" role="alert" hidden></p>
26
+ </main>
27
+ <script src="site-access.js"></script>
28
+ <script src="popup.js"></script>
29
+ </body>
30
+ </html>
@@ -0,0 +1,101 @@
1
+ const statusLine = document.getElementById('status')
2
+ const pairing = document.getElementById('pairing')
3
+ const codeInput = document.getElementById('pairing-code')
4
+ const connectButton = document.getElementById('connect')
5
+ const siteName = document.getElementById('site-name')
6
+ const siteDetail = document.getElementById('site-detail')
7
+ const allowButton = document.getElementById('allow')
8
+ const removeButton = document.getElementById('remove')
9
+ const errorLine = document.getElementById('error')
10
+ let currentOrigin = null
11
+ let currentPermission = null
12
+
13
+ function showError(message) {
14
+ errorLine.textContent = message
15
+ errorLine.hidden = !message
16
+ }
17
+
18
+ async function message(value) {
19
+ const response = await chrome.runtime.sendMessage(value)
20
+ if (!response?.ok) throw new Error(response?.error || 'The extension could not complete that action.')
21
+ return response.value
22
+ }
23
+
24
+ async function currentTab() {
25
+ const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true })
26
+ return tab || null
27
+ }
28
+
29
+ async function update() {
30
+ showError('')
31
+ const state = await message({ type: 'status' }).catch(() => ({ state: 'waiting', connected: false, paired: false }))
32
+ statusLine.textContent = state.connected ? (state.paused ? 'Connected · control paused' : 'Connected to Harness') : state.paired ? 'Paired · waiting for Harness' : 'Not connected to Harness'
33
+ pairing.hidden = state.paired
34
+
35
+ const tab = await currentTab()
36
+ currentOrigin = tab && !tab.incognito ? XingularSiteAccess.originOf(tab.url || '') : null
37
+ currentPermission = tab && !tab.incognito ? XingularSiteAccess.permissionFor(tab.url || '') : null
38
+ if (!currentOrigin || !currentPermission) {
39
+ siteName.textContent = 'No ordinary website tab selected'
40
+ siteDetail.textContent = tab?.incognito ? 'Incognito is never available to Xingular Agent.' : 'Open an http or https site in a normal Brave tab.'
41
+ allowButton.disabled = true
42
+ removeButton.hidden = true
43
+ return
44
+ }
45
+ let host = new URL(currentOrigin).host
46
+ try { host = new URL(tab.url).host } catch {}
47
+ siteName.textContent = host
48
+ const allowed = await XingularSiteAccess.allowed(tab.url)
49
+ siteDetail.textContent = allowed ? 'Xingular Agent can access normal tabs on this site.' : 'Allow this exact site before its tabs can be read or controlled.'
50
+ allowButton.hidden = allowed
51
+ allowButton.disabled = !state.connected
52
+ removeButton.hidden = !allowed
53
+ }
54
+
55
+ connectButton.addEventListener('click', async () => {
56
+ showError('')
57
+ const match = /^(\d{1,5}):([0-9a-f]{12})$/i.exec(codeInput.value.trim())
58
+ if (!match) return showError('Enter the port and code shown in Harness Browser settings.')
59
+ connectButton.disabled = true
60
+ try {
61
+ await message({ type: 'pair', port: Number(match[1]), code: match[2] })
62
+ codeInput.value = ''
63
+ await update()
64
+ } catch (error) {
65
+ showError(error.message)
66
+ } finally {
67
+ connectButton.disabled = false
68
+ }
69
+ })
70
+
71
+ allowButton.addEventListener('click', async () => {
72
+ showError('')
73
+ if (!currentOrigin) return
74
+ allowButton.disabled = true
75
+ try {
76
+ const granted = await XingularSiteAccess.grant(currentOrigin)
77
+ if (!granted) throw new Error('Brave did not grant access to this site.')
78
+ await message({ type: 'site-access-changed' })
79
+ await update()
80
+ } catch (error) {
81
+ showError(error.message)
82
+ allowButton.disabled = false
83
+ }
84
+ })
85
+
86
+ removeButton.addEventListener('click', async () => {
87
+ showError('')
88
+ if (!currentOrigin) return
89
+ removeButton.disabled = true
90
+ try {
91
+ await XingularSiteAccess.revoke(currentOrigin)
92
+ await message({ type: 'site-access-changed' })
93
+ await update()
94
+ } catch (error) {
95
+ showError(error.message)
96
+ } finally {
97
+ removeButton.disabled = false
98
+ }
99
+ })
100
+
101
+ void update()
@@ -0,0 +1,63 @@
1
+ (() => {
2
+ const originOf = address => {
3
+ try {
4
+ const url = new URL(address)
5
+ return ['http:', 'https:'].includes(url.protocol) ? url.origin : null
6
+ } catch {
7
+ return null
8
+ }
9
+ }
10
+
11
+ const permissionFor = address => {
12
+ const origin = originOf(address)
13
+ if (!origin) return null
14
+ const url = new URL(origin)
15
+ // Chrome host permissions do not scope to ports. The stored origin below does.
16
+ return `${url.protocol}//${url.hostname}/*`
17
+ }
18
+
19
+ const savedOrigins = async api => {
20
+ const saved = await api.storage.local.get('allowedOrigins')
21
+ return Array.isArray(saved.allowedOrigins)
22
+ ? [...new Set(saved.allowedOrigins.filter(origin => typeof origin === 'string' && originOf(origin) === origin))]
23
+ : []
24
+ }
25
+
26
+ const allowed = async (address, api = chrome) => {
27
+ const origin = originOf(address)
28
+ const permission = permissionFor(address)
29
+ if (!origin || !permission || !(await savedOrigins(api)).includes(origin)) return false
30
+ return api.permissions.contains({ origins: [permission] })
31
+ }
32
+
33
+ const grant = async (address, api = chrome) => {
34
+ const origin = originOf(address)
35
+ const permission = permissionFor(address)
36
+ if (!origin || !permission || !await api.permissions.request({ origins: [permission] })) return false
37
+ const origins = await savedOrigins(api)
38
+ if (!origins.includes(origin)) origins.push(origin)
39
+ await api.storage.local.set({ allowedOrigins: origins })
40
+ return true
41
+ }
42
+
43
+ const revoke = async (address, api = chrome) => {
44
+ const origin = originOf(address)
45
+ const permission = permissionFor(address)
46
+ if (!origin || !permission) return
47
+ const origins = (await savedOrigins(api)).filter(saved => saved !== origin)
48
+ await api.storage.local.set({ allowedOrigins: origins })
49
+ if (!origins.some(saved => permissionFor(saved) === permission)) {
50
+ await api.permissions.remove({ origins: [permission] }).catch(() => false)
51
+ }
52
+ }
53
+
54
+ const forgetRemoved = async (permissions, api = chrome) => {
55
+ const removed = new Set(Array.isArray(permissions?.origins) ? permissions.origins : [])
56
+ if (!removed.size) return
57
+ const origins = await savedOrigins(api)
58
+ const remaining = origins.filter(origin => !removed.has(permissionFor(origin)))
59
+ if (remaining.length !== origins.length) await api.storage.local.set({ allowedOrigins: remaining })
60
+ }
61
+
62
+ globalThis.XingularSiteAccess = Object.freeze({ originOf, permissionFor, allowed, grant, revoke, forgetRemoved })
63
+ })()