@dna113p/machines 0.1.0
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/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/codex/machines/ui/machines.html +286 -0
- package/dist/examples/agent.d.ts +1 -0
- package/dist/examples/agent.js +19 -0
- package/dist/examples/choices.d.ts +1 -0
- package/dist/examples/choices.js +17 -0
- package/dist/examples/host.d.ts +3 -0
- package/dist/examples/host.js +32 -0
- package/dist/examples/human.d.ts +1 -0
- package/dist/examples/human.js +17 -0
- package/dist/examples/named-agents.d.ts +1 -0
- package/dist/examples/named-agents.js +38 -0
- package/dist/examples/operation.d.ts +1 -0
- package/dist/examples/operation.js +10 -0
- package/dist/examples/pi.d.ts +1 -0
- package/dist/examples/pi.js +29 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.js +313 -0
- package/dist/pi-extension/index.d.ts +52 -0
- package/dist/pi-extension/index.js +390 -0
- package/dist/src/acp.d.ts +7 -0
- package/dist/src/acp.js +184 -0
- package/dist/src/agent-bindings.d.ts +15 -0
- package/dist/src/agent-bindings.js +113 -0
- package/dist/src/catalog.d.ts +31 -0
- package/dist/src/catalog.js +68 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +392 -0
- package/dist/src/discovery-worker.d.ts +3 -0
- package/dist/src/discovery-worker.js +52 -0
- package/dist/src/discovery.d.ts +8 -0
- package/dist/src/discovery.js +94 -0
- package/dist/src/host.d.ts +25 -0
- package/dist/src/host.js +357 -0
- package/dist/src/index.d.ts +210 -0
- package/dist/src/index.js +204 -0
- package/dist/src/launcher.d.ts +19 -0
- package/dist/src/launcher.js +98 -0
- package/dist/src/machine-module.d.ts +3 -0
- package/dist/src/machine-module.js +55 -0
- package/dist/src/session.d.ts +40 -0
- package/dist/src/session.js +165 -0
- package/dist/src/terminal-human.d.ts +2 -0
- package/dist/src/terminal-human.js +100 -0
- package/docs/architecture.md +111 -0
- package/docs/authoring.md +193 -0
- package/docs/integrations.md +142 -0
- package/package.json +58 -0
- package/skills/machine-builder/SKILL.md +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Machines contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Machines
|
|
2
|
+
|
|
3
|
+
Reusable workflows that coordinate agents, local operations, and human decisions.
|
|
4
|
+
|
|
5
|
+
A Machine makes a repeatable process explicit: ask an Agent to implement a change,
|
|
6
|
+
run an Operation to check it, and ask a Human to approve or provide feedback. Each
|
|
7
|
+
state returns an event; the Machine decides what happens next. XState handles the
|
|
8
|
+
transitions, while the same workflow can run from a terminal, Pi, or Codex.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
implement (Agent) → check (Operation) → review (Human) → done
|
|
12
|
+
↑ │
|
|
13
|
+
└──────────── feedback ────────────┘
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Use Machines when a sequence and its decision points are worth repeating. A
|
|
17
|
+
one-off prompt or shell script remains useful for work that needs no workflow.
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
Requires **Node.js 24 or later** and npm. From a checkout of this repository:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm ci
|
|
25
|
+
npm run check
|
|
26
|
+
npm run example
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The deterministic example needs no credentials or Agent installation and prints:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
calculate --completed--> done
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Here is its complete implementation, runnable as `demo.ts` in the checkout root:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { final, machine, operation, run } from "./src/index.ts";
|
|
39
|
+
|
|
40
|
+
const workflow = machine({
|
|
41
|
+
initial: "calculate",
|
|
42
|
+
states: {
|
|
43
|
+
calculate: operation(
|
|
44
|
+
() => ({ type: "completed" }),
|
|
45
|
+
{ completed: "done" },
|
|
46
|
+
),
|
|
47
|
+
done: final(),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = await run(workflow);
|
|
52
|
+
console.log(`calculate --completed--> ${String(result.value)}`);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Run it with `node demo.ts`. No build is needed to develop against the source.
|
|
56
|
+
|
|
57
|
+
| Example | Command | Requirements |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| Deterministic Operation | `npm run example` | Node only |
|
|
60
|
+
| Human input | `node examples/human.ts` | Terminal input |
|
|
61
|
+
| Suggestions and choices | `node examples/choices.ts` | Terminal input |
|
|
62
|
+
| Agent contract | `node examples/agent.ts` | Built-in fake runner |
|
|
63
|
+
| Agent escalation | `npm run example:agents` | Built-in fake runners |
|
|
64
|
+
| Child host and Human response | `npm run example:host` | Node only |
|
|
65
|
+
| Real Agent writes and verifies a file | `npm run example:pi` | Configured Pi through `pi-acp`; may use network and model credits |
|
|
66
|
+
|
|
67
|
+
## Run your own workflows
|
|
68
|
+
|
|
69
|
+
Save this as `.machines/hello.ts` in your project:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import type { MachinePrimitives } from "@dna113p/machines";
|
|
73
|
+
|
|
74
|
+
export const description = "Prints the supplied message without an Agent.";
|
|
75
|
+
|
|
76
|
+
export default function hello(
|
|
77
|
+
{ machine, operation, final }: MachinePrimitives,
|
|
78
|
+
input: string,
|
|
79
|
+
) {
|
|
80
|
+
return machine({
|
|
81
|
+
initial: "print",
|
|
82
|
+
states: {
|
|
83
|
+
print: operation(() => {
|
|
84
|
+
console.log(input || "Hello from Machines.");
|
|
85
|
+
return { type: "completed" };
|
|
86
|
+
}, { completed: "done" }),
|
|
87
|
+
done: final(),
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
From the checkout, run:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
./machine list
|
|
97
|
+
./machine show hello
|
|
98
|
+
./machine run hello "Hello from Machines."
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The command reports the Machine path and state progress, then prints `--> done`.
|
|
102
|
+
In another project, use the checkout's absolute `machine` path or install the npm
|
|
103
|
+
package below. The `MachinePrimitives` import is type-only; executing this definition
|
|
104
|
+
uses the primitives supplied by the launcher.
|
|
105
|
+
|
|
106
|
+
Discovery checks the nearest project `.machines/` and then `~/.machines/`.
|
|
107
|
+
Project definitions replace global definitions with the same name. Files can be
|
|
108
|
+
`name.ts` or `name/index.ts`; the single file wins if both exist. `agents.ts` is
|
|
109
|
+
reserved for Agent presets. Invalid definitions remain visible with their own
|
|
110
|
+
diagnostics; other valid Machines remain usable.
|
|
111
|
+
|
|
112
|
+
Read [Authoring Machines](docs/authoring.md) for Human input, Agent roles and
|
|
113
|
+
presets, feedback loops, and the programmatic launcher.
|
|
114
|
+
|
|
115
|
+
## Install from npm
|
|
116
|
+
|
|
117
|
+
Install the package in your project:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install @dna113p/machines
|
|
121
|
+
npx machine list
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Or run the CLI without adding a project dependency:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx @dna113p/machines list
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The command is still named `machine`. For a global installation, run
|
|
131
|
+
`npm install --global @dna113p/machines`, then use `machine list` or `machine run`.
|
|
132
|
+
|
|
133
|
+
To install a build from a checkout instead:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm ci
|
|
137
|
+
npm pack
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then install the generated `dna113p-machines-0.1.0.tgz` into a project:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install /absolute/path/to/machines/dna113p-machines-0.1.0.tgz
|
|
144
|
+
npx machine list
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The package contains JavaScript and TypeScript declarations. Application code can
|
|
148
|
+
import `{ machine, agent, human, operation, final, run }` from `"@dna113p/machines"`.
|
|
149
|
+
Workflow definitions remain ordinary TypeScript outside `node_modules`.
|
|
150
|
+
|
|
151
|
+
For **Codex MCP**, **the portable Codex plugin**, or **the Pi extension**, follow
|
|
152
|
+
[Integrations](docs/integrations.md). No personal marketplace or developer-only
|
|
153
|
+
installation script is required for the direct MCP setup.
|
|
154
|
+
|
|
155
|
+
## Execution model and limitations
|
|
156
|
+
|
|
157
|
+
Machine definitions and Agent preset files are **trusted executable code**.
|
|
158
|
+
Discovery imports them to inspect metadata, including their relative imports.
|
|
159
|
+
Keep module initialization free of side effects and do work inside states.
|
|
160
|
+
Disposable discovery workers refresh imported metadata; they are not a sandbox.
|
|
161
|
+
Operations and configured Agents run with the launching process's available access
|
|
162
|
+
and can modify files or contact external services.
|
|
163
|
+
|
|
164
|
+
Pi and MCP runs belong to the current session. Closing or reloading that session
|
|
165
|
+
terminates its owned hosts; runs are not persisted or resumable. Process shutdown
|
|
166
|
+
does not undo files or Git changes. A Machine owns its own retry, approval, merge,
|
|
167
|
+
and cleanup decisions. See [Architecture](docs/architecture.md) for ownership and
|
|
168
|
+
process-lifecycle limits.
|
|
169
|
+
|
|
170
|
+
ACP permission requests are currently unsupported and fail the Agent state. Choose
|
|
171
|
+
a harness configuration that can carry out the intended bounded task and put
|
|
172
|
+
workflow decisions in Human states. The launcher defaults unnamed Agents to
|
|
173
|
+
`npx -y pi-acp`; [Agent presets](docs/authoring.md#agent-presets) let you select
|
|
174
|
+
another ACP command or runner explicitly.
|
|
175
|
+
|
|
176
|
+
## Checks
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run check # typecheck and behavior tests
|
|
180
|
+
npm run build # JavaScript, declarations, examples, and UI assets
|
|
181
|
+
npm run smoke:package # install a tarball in a temporary project and exercise it
|
|
182
|
+
npm run build:plugin
|
|
183
|
+
npm run smoke:plugin # exercise a relocated standalone plugin
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,286 @@
|
|
|
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
|
+
<style>
|
|
7
|
+
:root {
|
|
8
|
+
color-scheme: light dark;
|
|
9
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
10
|
+
--bg: light-dark(#f7f7f5, #171715);
|
|
11
|
+
--panel: light-dark(#ffffff, #22221f);
|
|
12
|
+
--line: light-dark(#deded8, #3d3d38);
|
|
13
|
+
--text: light-dark(#20201d, #f1f1ec);
|
|
14
|
+
--muted: light-dark(#6d6d66, #aaa9a0);
|
|
15
|
+
--accent: light-dark(#287a6a, #62c8b1);
|
|
16
|
+
--warn: light-dark(#8a5b00, #f2c45e);
|
|
17
|
+
--bad: light-dark(#a2352a, #ff8d82);
|
|
18
|
+
--good: light-dark(#247342, #68cf8a);
|
|
19
|
+
}
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
body { margin: 0; color: var(--text); background: transparent; }
|
|
22
|
+
main {
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
border: 1px solid var(--line);
|
|
25
|
+
border-radius: 14px;
|
|
26
|
+
background: var(--panel);
|
|
27
|
+
box-shadow: 0 10px 28px color-mix(in srgb, #000 12%, transparent);
|
|
28
|
+
}
|
|
29
|
+
header { display: flex; align-items: center; gap: 10px; padding: 14px 16px 10px; }
|
|
30
|
+
.mark { color: var(--accent); font-size: 18px; line-height: 1; }
|
|
31
|
+
h1 { min-width: 0; flex: 1; margin: 0; overflow: hidden; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; }
|
|
32
|
+
.badge { border-radius: 999px; padding: 4px 8px; background: color-mix(in srgb, var(--accent) 13%, transparent); color: var(--accent); font-size: 12px; font-weight: 650; }
|
|
33
|
+
.badge.waiting { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
|
|
34
|
+
.badge.completed { background: color-mix(in srgb, var(--good) 14%, transparent); color: var(--good); }
|
|
35
|
+
.badge.failed { background: color-mix(in srgb, var(--bad) 14%, transparent); color: var(--bad); }
|
|
36
|
+
.body { padding: 0 16px 15px; }
|
|
37
|
+
.state { display: flex; align-items: baseline; gap: 8px; font-size: 14px; }
|
|
38
|
+
.state strong { font-weight: 680; }
|
|
39
|
+
.elapsed, .meta { color: var(--muted); font-size: 12px; }
|
|
40
|
+
.meta { margin-top: 5px; }
|
|
41
|
+
.human { margin-top: 13px; border-top: 1px solid var(--line); padding-top: 12px; }
|
|
42
|
+
.prompt { margin: 0 0 10px; font-size: 14px; line-height: 1.45; white-space: pre-wrap; }
|
|
43
|
+
.actions { display: flex; flex-wrap: wrap; gap: 7px; }
|
|
44
|
+
button, input { font: inherit; }
|
|
45
|
+
button {
|
|
46
|
+
border: 1px solid var(--line);
|
|
47
|
+
border-radius: 8px;
|
|
48
|
+
padding: 7px 10px;
|
|
49
|
+
color: var(--text);
|
|
50
|
+
background: var(--bg);
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
}
|
|
53
|
+
button:hover { border-color: var(--accent); }
|
|
54
|
+
button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
55
|
+
button:disabled, input:disabled { cursor: default; opacity: .55; }
|
|
56
|
+
form { display: flex; width: 100%; gap: 7px; margin-top: 8px; }
|
|
57
|
+
input { min-width: 0; flex: 1; border: 1px solid var(--line); border-radius: 8px; padding: 7px 9px; color: var(--text); background: var(--bg); }
|
|
58
|
+
.error { margin: 12px 0 0; color: var(--bad); font-size: 13px; line-height: 1.4; }
|
|
59
|
+
.done { margin: 11px 0 0; color: var(--muted); font-size: 13px; }
|
|
60
|
+
</style>
|
|
61
|
+
</head>
|
|
62
|
+
<body>
|
|
63
|
+
<main aria-live="polite">
|
|
64
|
+
<header>
|
|
65
|
+
<span class="mark" aria-hidden="true">◆</span>
|
|
66
|
+
<h1 id="machine">Machine</h1>
|
|
67
|
+
<span class="badge" id="status">starting</span>
|
|
68
|
+
</header>
|
|
69
|
+
<section class="body">
|
|
70
|
+
<div class="state"><strong id="state">starting</strong><span class="elapsed" id="elapsed"></span></div>
|
|
71
|
+
<div class="meta" id="agent"></div>
|
|
72
|
+
<div class="human" id="human" hidden>
|
|
73
|
+
<p class="prompt" id="prompt"></p>
|
|
74
|
+
<div class="actions" id="actions"></div>
|
|
75
|
+
</div>
|
|
76
|
+
<p class="error" id="error" hidden></p>
|
|
77
|
+
<p class="done" id="done" hidden></p>
|
|
78
|
+
</section>
|
|
79
|
+
</main>
|
|
80
|
+
<script>
|
|
81
|
+
const elements = Object.fromEntries(
|
|
82
|
+
["machine", "status", "state", "elapsed", "agent", "human", "prompt", "actions", "error", "done"]
|
|
83
|
+
.map((id) => [id, document.getElementById(id)])
|
|
84
|
+
);
|
|
85
|
+
const pending = new Map();
|
|
86
|
+
let requestId = 1;
|
|
87
|
+
let run;
|
|
88
|
+
let timer;
|
|
89
|
+
let busy = false;
|
|
90
|
+
let refreshing = false;
|
|
91
|
+
let closed = false;
|
|
92
|
+
let revision = 0;
|
|
93
|
+
let renderedHumanKey;
|
|
94
|
+
let responseError;
|
|
95
|
+
let statusError;
|
|
96
|
+
|
|
97
|
+
function callTool(name, args) {
|
|
98
|
+
if (window.openai?.callTool) return window.openai.callTool(name, args);
|
|
99
|
+
const id = requestId++;
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
pending.set(id, { resolve, reject });
|
|
102
|
+
window.parent.postMessage({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } }, "*");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toolData(result) {
|
|
107
|
+
const payload = result?.result ?? result;
|
|
108
|
+
if (payload?.isError) {
|
|
109
|
+
const message = payload.content?.filter((item) => item.type === "text").map((item) => item.text).join("\n");
|
|
110
|
+
throw new Error(message || "The Machine tool failed.");
|
|
111
|
+
}
|
|
112
|
+
const data = payload?.structuredContent;
|
|
113
|
+
if (!data?.run && !data?.runs?.length) throw new Error("The Machine tool did not return a run.");
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function receive(data) {
|
|
118
|
+
const next = data?.run ?? data?.runs?.[0];
|
|
119
|
+
if (!next || closed) return;
|
|
120
|
+
if (humanKey(next) !== humanKey(run)) responseError = undefined;
|
|
121
|
+
statusError = undefined;
|
|
122
|
+
revision += 1;
|
|
123
|
+
run = next;
|
|
124
|
+
render();
|
|
125
|
+
schedule();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function humanKey(snapshot) {
|
|
129
|
+
return snapshot?.status === "waiting" && snapshot.human
|
|
130
|
+
? JSON.stringify([snapshot.id, snapshot.human.requestId])
|
|
131
|
+
: undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function renderError() {
|
|
135
|
+
const error = run?.error ?? responseError ?? statusError;
|
|
136
|
+
elements.error.hidden = !error;
|
|
137
|
+
elements.error.textContent = error ?? "";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function render() {
|
|
141
|
+
const terminal = run.status === "completed" || run.status === "failed";
|
|
142
|
+
elements.machine.textContent = run.machine;
|
|
143
|
+
elements.status.textContent = run.status === "waiting" ? "input needed" : run.status;
|
|
144
|
+
elements.status.className = `badge ${run.status}`;
|
|
145
|
+
elements.state.textContent = typeof run.state === "string" ? run.state : run.state ? JSON.stringify(run.state) : "starting";
|
|
146
|
+
elements.elapsed.textContent = `${run.elapsedSeconds}s`;
|
|
147
|
+
elements.agent.textContent = run.agent
|
|
148
|
+
? [run.agent.harness, run.agent.model, run.agent.thinking ? `thinking ${run.agent.thinking}` : undefined].filter(Boolean).join(" · ")
|
|
149
|
+
: "";
|
|
150
|
+
renderError();
|
|
151
|
+
elements.done.hidden = !terminal || Boolean(run.error);
|
|
152
|
+
elements.done.textContent = run.status === "completed" ? "Finished." : "";
|
|
153
|
+
renderHuman();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function renderHuman() {
|
|
157
|
+
const human = run.status === "waiting" ? run.human : undefined;
|
|
158
|
+
elements.human.hidden = !human;
|
|
159
|
+
const key = humanKey(run);
|
|
160
|
+
if (key === renderedHumanKey) {
|
|
161
|
+
for (const control of elements.actions.querySelectorAll("button, input")) control.disabled = busy;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
renderedHumanKey = key;
|
|
165
|
+
elements.actions.replaceChildren();
|
|
166
|
+
if (!human) return;
|
|
167
|
+
elements.prompt.textContent = human.prompt;
|
|
168
|
+
const target = { runId: run.id, requestId: human.requestId };
|
|
169
|
+
const values = human.choices ?? human.suggestions ?? [];
|
|
170
|
+
for (const value of values) {
|
|
171
|
+
const button = document.createElement("button");
|
|
172
|
+
button.type = "button";
|
|
173
|
+
button.textContent = value;
|
|
174
|
+
button.disabled = busy;
|
|
175
|
+
button.onclick = () => respond(target, value);
|
|
176
|
+
elements.actions.append(button);
|
|
177
|
+
}
|
|
178
|
+
if (!human.choices) {
|
|
179
|
+
const form = document.createElement("form");
|
|
180
|
+
const input = document.createElement("input");
|
|
181
|
+
const submit = document.createElement("button");
|
|
182
|
+
input.placeholder = "Other response";
|
|
183
|
+
input.setAttribute("aria-label", "Machine response");
|
|
184
|
+
input.disabled = busy;
|
|
185
|
+
submit.type = "submit";
|
|
186
|
+
submit.textContent = "Send";
|
|
187
|
+
submit.disabled = busy;
|
|
188
|
+
form.append(input, submit);
|
|
189
|
+
form.onsubmit = (event) => {
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
if (input.value !== "") respond(target, input.value);
|
|
192
|
+
};
|
|
193
|
+
elements.actions.append(form);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function respond(target, response) {
|
|
198
|
+
const key = JSON.stringify([target.runId, target.requestId]);
|
|
199
|
+
if (closed || busy || key !== humanKey(run)) return;
|
|
200
|
+
const focused = elements.actions.contains(document.activeElement) ? document.activeElement : undefined;
|
|
201
|
+
busy = true;
|
|
202
|
+
responseError = undefined;
|
|
203
|
+
const startedAt = ++revision;
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
renderHuman();
|
|
206
|
+
renderError();
|
|
207
|
+
try {
|
|
208
|
+
const result = await callTool("machine_respond", { ...target, response });
|
|
209
|
+
if (!closed && revision === startedAt) receive(toolData(result));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (!closed && key === humanKey(run)) {
|
|
212
|
+
responseError = error?.message ?? String(error);
|
|
213
|
+
renderError();
|
|
214
|
+
}
|
|
215
|
+
} finally {
|
|
216
|
+
busy = false;
|
|
217
|
+
if (!closed) {
|
|
218
|
+
renderHuman();
|
|
219
|
+
if (focused && elements.actions.contains(focused) && (!document.activeElement || document.activeElement === document.body)) focused.focus();
|
|
220
|
+
schedule();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function schedule(delay = 1000) {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
if (closed || busy || refreshing || !run || run.status === "completed" || run.status === "failed") return;
|
|
228
|
+
timer = setTimeout(refresh, delay);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function refresh() {
|
|
232
|
+
if (closed || refreshing || busy || !run) return;
|
|
233
|
+
refreshing = true;
|
|
234
|
+
const startedAt = revision;
|
|
235
|
+
let delay = 1000;
|
|
236
|
+
try {
|
|
237
|
+
const result = await callTool("machine_status", { runId: run.id });
|
|
238
|
+
if (!closed && revision === startedAt) {
|
|
239
|
+
const data = toolData(result);
|
|
240
|
+
receive(data);
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (!closed && revision === startedAt) {
|
|
244
|
+
statusError = error?.message ?? String(error);
|
|
245
|
+
renderError();
|
|
246
|
+
delay = 3000;
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
refreshing = false;
|
|
250
|
+
schedule(delay);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
window.addEventListener("message", (event) => {
|
|
255
|
+
if (closed || event.source !== window.parent) return;
|
|
256
|
+
const message = event.data;
|
|
257
|
+
if (!message || message.jsonrpc !== "2.0") return;
|
|
258
|
+
if (message.id !== undefined && pending.has(message.id)) {
|
|
259
|
+
const request = pending.get(message.id);
|
|
260
|
+
pending.delete(message.id);
|
|
261
|
+
if (message.error) request.reject(message.error);
|
|
262
|
+
else request.resolve(message.result);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (message.method === "ui/notifications/tool-result") {
|
|
266
|
+
try {
|
|
267
|
+
receive(toolData(message.params));
|
|
268
|
+
} catch (error) {
|
|
269
|
+
statusError = error?.message ?? String(error);
|
|
270
|
+
renderError();
|
|
271
|
+
schedule(3000);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}, { passive: true });
|
|
275
|
+
|
|
276
|
+
window.addEventListener("pagehide", () => {
|
|
277
|
+
closed = true;
|
|
278
|
+
clearTimeout(timer);
|
|
279
|
+
for (const request of pending.values()) request.reject(new Error("The Machine card was closed."));
|
|
280
|
+
pending.clear();
|
|
281
|
+
}, { once: true });
|
|
282
|
+
|
|
283
|
+
receive(window.openai?.toolOutput);
|
|
284
|
+
</script>
|
|
285
|
+
</body>
|
|
286
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { agent, final, human, machine, operation, run, } from "../src/index.js";
|
|
2
|
+
const fakeAgent = (request) => {
|
|
3
|
+
console.log("Agent request:");
|
|
4
|
+
console.log(`prompt: ${request.prompt}`);
|
|
5
|
+
console.log(`cwd: ${request.cwd}`);
|
|
6
|
+
console.log(`outcomes: ${request.outcomes.join(", ")}`);
|
|
7
|
+
return { type: "completed" };
|
|
8
|
+
};
|
|
9
|
+
const example = machine({
|
|
10
|
+
initial: "work",
|
|
11
|
+
states: {
|
|
12
|
+
work: agent("Implement the tiny task", { completed: "confirm" }, { cwd: "/tmp/machines-fake" }),
|
|
13
|
+
confirm: human("Continue to verification?", { submitted: "verify" }),
|
|
14
|
+
verify: operation(() => ({ type: "passed" }), { passed: "done" }),
|
|
15
|
+
done: final(),
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const result = await run(example, { agents: { default: fakeAgent } });
|
|
19
|
+
console.log(`verified --> ${String(result.value)}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { final, human, machine, run, } from "../src/index.js";
|
|
2
|
+
const example = machine({
|
|
3
|
+
initial: "choose",
|
|
4
|
+
states: {
|
|
5
|
+
choose: human("Approve or deny?", {
|
|
6
|
+
submitted: {
|
|
7
|
+
target: "done",
|
|
8
|
+
actions: ({ event }) => {
|
|
9
|
+
console.log(`submitted "${String(event.value)}"`);
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
}, { choices: ["approve", "deny"] }),
|
|
13
|
+
done: final(),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const result = await run(example);
|
|
17
|
+
console.log(`--> ${String(result.value)}`);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { MachinePrimitives } from "../src/index.ts";
|
|
2
|
+
export declare const description = "Demonstrates one asynchronous child-hosted Machine.";
|
|
3
|
+
export default function hostedExample({ final, human, machine, operation }: MachinePrimitives): ReturnType<MachinePrimitives["machine"]>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { startMachineHost } from "../src/host.js";
|
|
3
|
+
export const description = "Demonstrates one asynchronous child-hosted Machine.";
|
|
4
|
+
export default function hostedExample({ final, human, machine, operation }) {
|
|
5
|
+
return machine({
|
|
6
|
+
initial: "work",
|
|
7
|
+
states: {
|
|
8
|
+
work: operation(async () => {
|
|
9
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
10
|
+
return { type: "completed" };
|
|
11
|
+
}, { completed: "review" }),
|
|
12
|
+
review: human("Approve the hosted example?", { submitted: "done" }, { choices: ["approve", "deny"] }),
|
|
13
|
+
done: final(),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
18
|
+
let resolveHuman;
|
|
19
|
+
const human = new Promise((resolve) => {
|
|
20
|
+
resolveHuman = resolve;
|
|
21
|
+
});
|
|
22
|
+
const hosted = await startMachineHost({
|
|
23
|
+
machine: fileURLToPath(import.meta.url),
|
|
24
|
+
onState: (state) => console.log(`state: ${String(state)}`),
|
|
25
|
+
onHumanRequest: resolveHuman,
|
|
26
|
+
});
|
|
27
|
+
console.log(`started: ${hosted.name}`);
|
|
28
|
+
const request = await human;
|
|
29
|
+
console.log(`human: ${request.prompt}`);
|
|
30
|
+
await hosted.respond("approve", request.requestId);
|
|
31
|
+
console.log(`completed: ${String((await hosted.result).state)}`);
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { final, human, machine, run, } from "../src/index.js";
|
|
2
|
+
const example = machine({
|
|
3
|
+
initial: "choose",
|
|
4
|
+
states: {
|
|
5
|
+
choose: human("What should happen?", {
|
|
6
|
+
submitted: {
|
|
7
|
+
target: "done",
|
|
8
|
+
actions: ({ event }) => {
|
|
9
|
+
console.log(`submitted "${String(event.value)}"`);
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
}, { suggestions: ["Use the default"] }),
|
|
13
|
+
done: final(),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const result = await run(example);
|
|
17
|
+
console.log(`--> ${String(result.value)}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { agent, final, machine, operation, run, } from "../src/index.js";
|
|
2
|
+
let failedReviews = 0;
|
|
3
|
+
const fastReviewer = (request, report) => {
|
|
4
|
+
report?.({
|
|
5
|
+
type: "identity",
|
|
6
|
+
harness: "antigravity",
|
|
7
|
+
model: "gemini-flash",
|
|
8
|
+
});
|
|
9
|
+
console.log(`fastReviewer: ${request.prompt}`);
|
|
10
|
+
return { type: "changesRequested" };
|
|
11
|
+
};
|
|
12
|
+
const strongReviewer = (request, report) => {
|
|
13
|
+
report?.({
|
|
14
|
+
type: "identity",
|
|
15
|
+
harness: "claude-code",
|
|
16
|
+
model: "opus-5",
|
|
17
|
+
thinking: "high",
|
|
18
|
+
});
|
|
19
|
+
console.log(`strongReviewer: ${request.prompt}`);
|
|
20
|
+
return { type: "approved" };
|
|
21
|
+
};
|
|
22
|
+
const example = machine({
|
|
23
|
+
initial: "review",
|
|
24
|
+
states: {
|
|
25
|
+
review: agent("Review the implementation quickly", { changesRequested: "countFailure" }, { using: "fastReviewer" }),
|
|
26
|
+
countFailure: operation(() => {
|
|
27
|
+
failedReviews += 1;
|
|
28
|
+
return { type: failedReviews >= 2 ? "escalate" : "retry" };
|
|
29
|
+
}, { retry: "review", escalate: "escalatedReview" }),
|
|
30
|
+
escalatedReview: agent("Perform an escalated review", { approved: "done" }, { using: "strongReviewer" }),
|
|
31
|
+
done: final(),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const result = await run(example, {
|
|
35
|
+
agents: { fastReviewer, strongReviewer },
|
|
36
|
+
});
|
|
37
|
+
console.log(`Failed fast reviews: ${failedReviews}`);
|
|
38
|
+
console.log(`--> ${String(result.value)}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { final, machine, operation, run, } from "../src/index.js";
|
|
2
|
+
const example = machine({
|
|
3
|
+
initial: "calculate",
|
|
4
|
+
states: {
|
|
5
|
+
calculate: operation(() => ({ type: "completed" }), { completed: "done" }),
|
|
6
|
+
done: final(),
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
const result = await run(example);
|
|
10
|
+
console.log(`calculate --completed--> ${String(result.value)}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|