Skip to content

Commit 35cc2dd

Browse files
committed
build: initial OSS commit
Hello world from Replica!
0 parents  commit 35cc2dd

1,862 files changed

Lines changed: 223324 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
10 KB
Loading

‎.github/dependabot.yml‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: bundler
4+
directory: "/hub"
5+
schedule:
6+
interval: weekly
7+
cooldown:
8+
default-days: 7
9+
- package-ecosystem: npm
10+
directories:
11+
- "/hub"
12+
- "/actions/build"
13+
- "/starter"
14+
schedule:
15+
interval: weekly
16+
cooldown:
17+
default-days: 7
18+
- package-ecosystem: github-actions
19+
directory: "/"
20+
schedule:
21+
interval: weekly
22+
cooldown:
23+
default-days: 7

‎.github/extensions/replica-prototype/README.md‎

Lines changed: 293 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { randomUUID } from "node:crypto"
2+
3+
// Allow the 8 KiB Controls query plus the underlying prototype route.
4+
export const MAX_RELOAD_ADDRESS_LENGTH = 16_384
5+
6+
export function agentCommands() {
7+
let pending = null
8+
const queue = []
9+
const history = []
10+
11+
function stage(command, name) {
12+
command.trace.push({ stage: name, elapsedMs: Date.now() - command.queuedAt })
13+
}
14+
function diagnostic(command) {
15+
return {
16+
id: command.id,
17+
type: command.type,
18+
rendererId: command.rendererId ?? null,
19+
outcome: command.outcome ?? "pending",
20+
stages: command.trace.map((entry) => ({ ...entry })),
21+
}
22+
}
23+
24+
function startNext() {
25+
if (pending || !queue.length) return
26+
const command = queue.shift()
27+
const timer = setTimeout(() => {
28+
pending = null
29+
command.outcome = "timeout"
30+
command.reject(
31+
new Error(
32+
`The live canvas did not respond. Request ${command.id}; last stage: ${command.trace.at(-1).stage}. Keep its panel open and try again.`,
33+
),
34+
)
35+
startNext()
36+
}, command.timeoutMs)
37+
command.timer = timer
38+
pending = command
39+
}
40+
41+
return {
42+
invoke(type, input = {}, timeoutMs = 5_000) {
43+
return new Promise((resolve, reject) => {
44+
const command = {
45+
id: randomUUID(),
46+
type,
47+
input,
48+
timeoutMs,
49+
resolve,
50+
reject,
51+
queuedAt: Date.now(),
52+
trace: [],
53+
}
54+
stage(command, "queued")
55+
history.push(command)
56+
if (history.length > 20) history.shift()
57+
queue.push(command)
58+
startNext()
59+
})
60+
},
61+
next(rendererId) {
62+
if (
63+
rendererId !== undefined &&
64+
(typeof rendererId !== "string" || !/^[a-zA-Z0-9-]{1,64}$/.test(rendererId))
65+
)
66+
throw new Error("Invalid canvas renderer ID.")
67+
if (!pending || pending.sent) return null
68+
pending.sent = true
69+
pending.rendererId = rendererId
70+
stage(pending, "delivered")
71+
return { id: pending.id, type: pending.type, input: pending.input }
72+
},
73+
progress({ id, stage: name }, rendererId) {
74+
if (!pending || pending.id !== id || pending.rendererId !== rendererId)
75+
throw new Error("No matching canvas action is pending for this renderer.")
76+
const stages = ["handler started", "status collected"]
77+
if (!stages.includes(name)) throw new Error("Invalid canvas action stage.")
78+
if (!pending.trace.some((entry) => entry.stage === name)) stage(pending, name)
79+
},
80+
diagnostics() {
81+
return { requests: history.map(diagnostic) }
82+
},
83+
complete(result, rendererId) {
84+
if (!pending || result?.id !== pending.id || typeof result.ok !== "boolean") {
85+
throw new Error("No matching canvas action is pending.")
86+
}
87+
if (pending.rendererId !== rendererId)
88+
throw new Error("This canvas action belongs to another renderer.")
89+
const { resolve, reject, timer } = pending
90+
stage(pending, "reply received")
91+
pending.outcome = result.ok ? "completed" : "failed"
92+
clearTimeout(timer)
93+
pending = null
94+
if (result.ok) resolve(result.value)
95+
else
96+
reject(new Error(typeof result.error === "string" ? result.error : "Canvas action failed."))
97+
startNext()
98+
},
99+
close() {
100+
const error = new Error("The canvas was closed or changed during this action.")
101+
if (pending) {
102+
pending.outcome = "cancelled"
103+
clearTimeout(pending.timer)
104+
pending.reject(error)
105+
pending = null
106+
}
107+
for (const command of queue) {
108+
command.outcome = "cancelled"
109+
command.reject(error)
110+
}
111+
queue.length = 0
112+
},
113+
}
114+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import assert from "node:assert/strict"
2+
import { it } from "node:test"
3+
4+
import { agentCommands } from "./index.mjs"
5+
6+
it("delivers queued actions in order and matches each response ID", async () => {
7+
const commands = agentCommands()
8+
const result = commands.invoke("inspect")
9+
const command = commands.next()
10+
assert.equal(command.type, "inspect")
11+
assert.equal(commands.next(), null)
12+
const following = commands.invoke("click")
13+
assert.equal(commands.next(), null)
14+
assert.throws(() => commands.complete({ id: "wrong", ok: true }), /No matching/)
15+
commands.complete({ id: command.id, ok: true, value: { addressDraft: "/draft" } })
16+
assert.deepEqual(await result, { addressDraft: "/draft" })
17+
const click = commands.next()
18+
assert.equal(click.type, "click")
19+
commands.complete({ id: click.id, ok: true, value: { clicked: true } })
20+
assert.deepEqual(await following, { clicked: true })
21+
})
22+
23+
it("rejects failed actions and cancels pending and queued work when the panel closes", async () => {
24+
const commands = agentCommands()
25+
const failed = commands.invoke("click")
26+
const command = commands.next()
27+
commands.complete({ id: command.id, ok: false, error: "Target hidden." })
28+
await assert.rejects(failed, /Target hidden/)
29+
const pending = commands.invoke("inspect")
30+
const queued = commands.invoke("controls-status")
31+
const cancelQueued = assert.rejects(queued, /closed or changed/)
32+
commands.close()
33+
await assert.rejects(pending, /closed or changed/)
34+
await cancelQueued
35+
assert.equal(commands.next(), null)
36+
})
37+
38+
it("allows a bounded readiness wait beyond the ordinary transport timeout", async (t) => {
39+
t.mock.timers.enable({ apis: ["setTimeout"] })
40+
const commands = agentCommands()
41+
const result = commands.invoke("controls-status", { timeoutMs: 10000 }, 15000)
42+
const command = commands.next()
43+
t.mock.timers.tick(6000)
44+
commands.complete({ id: command.id, ok: true, value: { status: "ready" } })
45+
assert.deepEqual(await result, { status: "ready" })
46+
const timeout = assert.rejects(
47+
commands.invoke("controls-status", { timeoutMs: 10000 }, 15000),
48+
/did not respond/,
49+
)
50+
t.mock.timers.tick(15000)
51+
await timeout
52+
})
53+
54+
it("traces the owning renderer, rejects competing replies and reports the last stage on timeout", async (t) => {
55+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] })
56+
const commands = agentCommands()
57+
const failure = assert.rejects(
58+
commands.invoke("controls-status", {}, 5000),
59+
/last stage: handler started/,
60+
)
61+
const command = commands.next("native-renderer")
62+
assert.equal(commands.next("browser-renderer"), null)
63+
assert.throws(
64+
() => commands.progress({ id: command.id, stage: "status collected" }, "browser-renderer"),
65+
/No matching/,
66+
)
67+
commands.progress({ id: command.id, stage: "handler started" }, "native-renderer")
68+
assert.throws(
69+
() => commands.complete({ id: command.id, ok: true }, "browser-renderer"),
70+
/another renderer/,
71+
)
72+
t.mock.timers.tick(5000)
73+
await failure
74+
assert.deepEqual(commands.diagnostics().requests[0], {
75+
id: command.id,
76+
type: "controls-status",
77+
rendererId: "native-renderer",
78+
outcome: "timeout",
79+
stages: [
80+
{ stage: "queued", elapsedMs: 0 },
81+
{ stage: "delivered", elapsedMs: 0 },
82+
{ stage: "handler started", elapsedMs: 0 },
83+
],
84+
})
85+
86+
it("bounds diagnostic history and excludes request inputs and reply values", async () => {
87+
const commands = agentCommands()
88+
for (let index = 0; index < 25; index++) {
89+
const result = commands.invoke("inspect", { value: "PRIVATE_INPUT" })
90+
const command = commands.next("renderer")
91+
commands.complete({ id: command.id, ok: true, value: "PRIVATE_REPLY" }, "renderer")
92+
await result
93+
}
94+
const diagnostics = commands.diagnostics()
95+
assert.equal(diagnostics.requests.length, 20)
96+
assert.ok(!JSON.stringify(diagnostics).includes("PRIVATE"))
97+
commands.close()
98+
const result = commands.invoke("controls-status")
99+
const command = commands.next("reloaded-renderer")
100+
commands.complete(
101+
{ id: command.id, ok: true, value: { status: "pending" } },
102+
"reloaded-renderer",
103+
)
104+
assert.deepEqual(await result, { status: "pending" })
105+
})
106+
})
166 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.

‎.github/extensions/replica-prototype/assets/canvas-bridge.js‎

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.github/extensions/replica-prototype/assets/controls-validator.mjs‎

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)