@jc_stack/ez-agents 0.1.0-beta.49 → 0.1.0-beta.50
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/AGENTS.md +8 -6
- package/CHANGELOG.md +15 -0
- package/README.md +9 -5
- package/bin/ez +16 -7
- package/docs/architecture/ai-selection.md +4 -0
- package/docs/docker-runtime.md +18 -2
- package/docs/plugin-guide.md +3 -3
- package/docs/plugins.md +13 -10
- package/docs/scheduling.md +55 -18
- package/docs/setup.md +2 -2
- package/docs/standalone-cli.md +4 -4
- package/docs/upgrades.md +9 -13
- package/package.json +1 -1
- package/src/agent-guidance.ts +2 -2
- package/src/ai.ts +17 -5
- package/src/application-channel.ts +12 -8
- package/src/executor.ts +9 -0
- package/src/host-executor-client.ts +19 -7
- package/src/index.ts +125 -21
- package/src/menu.ts +3 -2
- package/src/plugins/manager.mjs +43 -40
- package/src/runs.ts +61 -3
- package/src/schedule-cli.ts +18 -12
- package/src/scheduled-tasks.ts +42 -8
- package/src/scheduler.ts +1 -1
- package/src/session-close.ts +17 -0
- package/src/setup-fallback.ts +62 -25
- package/src/tools-line.d.mts +1 -0
- package/src/tools-line.mjs +3 -0
- package/src/updates/binding.mjs +4 -10
- package/src/updates/control.mjs +3 -2
- package/src/updates/supervisor.mjs +1 -1
- package/src/workspace.ts +12 -1
- package/test/ai.test.ts +48 -2
- package/test/application-channel.test.ts +24 -0
- package/test/application-controls.test.ts +4 -1
- package/test/intake-relay.test.ts +63 -2
- package/test/model-policy.test.ts +1 -1
- package/test/plugin-broker.test.mjs +48 -1
- package/test/plugin-manager.test.mjs +52 -38
- package/test/run-handoff.test.ts +132 -0
- package/test/scheduled-tasks.test.ts +24 -2
- package/test/session-close.test.ts +17 -0
- package/test/setup-fallback.test.ts +194 -16
- package/test/updates.test.mjs +19 -10
- package/test/workspace.test.ts +6 -0
package/AGENTS.md
CHANGED
|
@@ -16,11 +16,13 @@ through one generic transport, with separate agent workspaces and sessions.
|
|
|
16
16
|
|
|
17
17
|
## Operating-agent instructions
|
|
18
18
|
|
|
19
|
-
Do not inject a coding handbook into production agent minds. The
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
Do not inject a coding handbook into production agent minds. The only
|
|
20
|
+
package-written `AGENTS.md` text is the initial workspace seed: purpose, a short
|
|
21
|
+
engine-neutral `memory/` note and one static line pointing at
|
|
22
|
+
`ez tools list --details`. After seeding the agent owns `AGENTS.md`; inventory is
|
|
23
|
+
generated on read, so do not hardcode command recipes. The one transport-level
|
|
24
|
+
lifecycle prompt is the silent conversation-close turn (`src/session-close.ts`),
|
|
25
|
+
which runs only for owner conversations. Coding defaults for this repository stay in this file and
|
|
24
26
|
CONTRIBUTING.md.
|
|
25
27
|
|
|
26
28
|
## Installing this package
|
|
@@ -105,7 +107,7 @@ If no answer identifies a material blocker, finish. Do not start another review
|
|
|
105
107
|
round merely to seek more criticism, tests or hypothetical edge cases.
|
|
106
108
|
|
|
107
109
|
## 3. Crash-Safe Disk State
|
|
108
|
-
- Persistent authority state (`ControlStore` owner/pairing/sessions/presets, application bindings, schedule definitions) stays in disk-backed JSON files. Run/outbox/inbox bookkeeping is relay memory reached through the delivery socket (`src/delivery-socket.ts`): a restart drops in-flight runs and queued deliveries by design, uncertain sends report unknown rather than success, and Telegram redelivery is the replay mechanism. The live delivery socket also replaces the relay flock as the single-relay guard.
|
|
110
|
+
- Persistent authority state (`ControlStore` owner/pairing/sessions/presets, application bindings, schedule definitions) stays in disk-backed JSON files. Run/outbox/inbox bookkeeping is relay memory reached through the delivery socket (`src/delivery-socket.ts`): a restart drops in-flight runs and queued deliveries by design, uncertain sends report unknown rather than success, and Telegram redelivery is the replay mechanism. The one exception: a graceful stop hands running host-transport runs, whose executors outlive the relay, to the next relay through private `control/run-handoff.json`; that relay adopts and watches them and never relaunches one. The live delivery socket also replaces the relay flock as the single-relay guard.
|
|
109
111
|
- **Atomic write pattern:** Never write directly to a state file. Always write to a temporary file (`${target}.${process.pid}.tmp`) with mode `0o600`, then atomically `rename` it over the destination.
|
|
110
112
|
- **Path sanitization:** Every ID parameter (`runId`, `outboxId`, `pairingId`) must be validated against `/^[a-zA-Z0-9_-]+$/` before being joined into paths. Never allow directory traversal (`..`).
|
|
111
113
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.50
|
|
4
|
+
|
|
5
|
+
- Share memory across engines through the workspace. New workspaces seed a short `## Memory` note (durable facts in `memory/`, one per file, indexed in `memory/MEMORY.md`); every engine loads `AGENTS.md`, so Codex and Claude read the same files. When a chat conversation is replaced (`/new`, the New button, web app new, or an AI/provider switch), its engine gets one silent turn in that native session to save durable facts; nothing is delivered and the engine decides what to write.
|
|
6
|
+
- Drop the managed `<!-- ez tools -->` AGENTS.md footer. The install seed writes one static line ("Tools: run `ez tools list --details`; each plugin's `--help` gives usage."); update binding replaces an existing footer with that line once and otherwise never writes instructions. `ez tools list --details` now prints per plugin `aliases`, `purpose`, `skills`, `skillReads` and one `example` call (optional manifest `example`, else `<alias> --help`).
|
|
7
|
+
- Stop reporting a live plugin call as a stale lease on isolated agents. Leases record their PID namespace; a lease written in the broker container is judged by its call container's running state, so `ez updates apply` no longer refuses during in-flight calls. Apply and recover only queue the job; main activation now drains in-flight calls before switching instead of failing.
|
|
8
|
+
- Keep host-capable native runs alive across a graceful relay restart. The stopping relay hands running host-transport runs to the next relay instead of cancelling them; it adopts and watches each run with its schedule hold, concurrency slot and message delivery, and records its exit status. Nothing is relaunched; a run that cannot be adopted or whose outcome is unknown is recorded failed and interrupted, holding its schedule, and a run that fails adoption is asked to stop. Core upgrades still wait for idle native work; isolated agents, crashes and queued work keep the previous behavior.
|
|
9
|
+
- Continue a scheduled occurrence on its next saved login when Claude stops at a usage/session limit after work began. The next setup starts a fresh native session with the saved prompt plus one factual line naming the run, stopped setup, its native session and reset time. Only a typed quota rejection the run ended on continues; work after a limit, access denial, other failures, cancellation and unknown outcomes still stop without replay. Attempts record `quota-after-work-began` and each Ez-assigned session ID.
|
|
10
|
+
- Stop isolated plugin calls hanging silently before reaching the broker when the caller leaves a socket stdin open (native engine shells, async Node spawns). Pipes and files are still read to EOF; a socket that sends nothing for one second is treated as no input. Remove a uniquely named call container whenever Compose exits non-zero, so a call that stops between create and start no longer strands it in `Created`.
|
|
11
|
+
- Advance scheduled work to its next saved login after a typed authentication rejection before work. Recognize Claude API-error metadata without marking analysis started; account/organization denials, hooks, model/tool work and uncertain outcomes still stop fallback. Preserve the saved chain for the next occurrence.
|
|
12
|
+
- Let scheduled fallback setups name an owner-provisioned native login profile per setup, with no fixed list length. A quota rejection skips later setups on the same login profile while other accounts stay eligible; model/tool/hook work, access denial, cancellation and uncertain failures still stop the chain.
|
|
13
|
+
- Show each scheduled task's frequency and full primary → fallback path (CLI, login profile, provider, model, effort) in Telegram `/tasks` and `/tasks N`.
|
|
14
|
+
- Report the exact AI selection error (missing client models, missing model, unsupported effort) instead of a generic invalid selection, without changing the saved choice.
|
|
15
|
+
|
|
16
|
+
Beta limits: live fresh-host/reboot acceptance, a live host-capable relay recreate during a running native run, two-real-account channel switching and real exhausted-account/provider rejection remain pending. An adopted scheduled occurrence does not advance to fallback setups, and messages a run sends while no relay is up still fail. The conversation close turn is engine-decided and runs only for owner conversations; existing workspaces do not receive the `## Memory` seed note automatically. Plugin manifests may declare `example`; a plugin that does requires core 0.1.0-beta.50 or newer, and older cores refuse that plugin update (manifest validation) and keep the installed version until core upgrades. Publication does not establish an installed-agent upgrade or authorize provider/domain writes.
|
|
17
|
+
|
|
3
18
|
## 0.1.0-beta.49
|
|
4
19
|
|
|
5
20
|
- Upgrade compatible plugins while native work continues, draining in-flight plugin calls for activation and preserving rollback. Core updates still wait for idle.
|
package/README.md
CHANGED
|
@@ -172,7 +172,8 @@ transport, with separate agent workspaces and sessions. See
|
|
|
172
172
|
|
|
173
173
|
Telegram `/status` shows the running relay and host versions plus installed
|
|
174
174
|
plugin versions. Its read-only **Scheduled tasks** control lists the paired
|
|
175
|
-
owner's active schedules in alphabetical order, with their
|
|
175
|
+
owner's active schedules in alphabetical order, with their frequency/timezone
|
|
176
|
+
and ordered CLI/login-profile/model/effort execution path (primary → fallbacks),
|
|
176
177
|
next occurrence or current queued/running state, and a short preview of the
|
|
177
178
|
saved invocation prompt; it never creates, changes or runs a task. The agent's `ez status` adds verified plugin
|
|
178
179
|
runtime states and upgrade job receipts. See [status and upgrades](docs/upgrades.md).
|
|
@@ -260,11 +261,14 @@ restart and shutdown. Host-capable agents register the small host CLI transport
|
|
|
260
261
|
|
|
261
262
|
The selected host CLI runs in this agent's persistent workspace and owns its
|
|
262
263
|
Markdown/work files. New conversations and AI changes preserve those files.
|
|
263
|
-
One compact `AGENTS.md` is seeded with the installer purpose
|
|
264
|
-
|
|
264
|
+
One compact `AGENTS.md` is seeded with the installer purpose, a short note to
|
|
265
|
+
keep durable facts in `memory/` (one fact per file, indexed in `memory/MEMORY.md`)
|
|
266
|
+
and one line pointing at `ez tools list --details`. Every engine loads `AGENTS.md`,
|
|
267
|
+
so they share that memory. When a chat conversation is replaced (new conversation
|
|
268
|
+
or AI/provider change), its engine gets one silent turn to save durable facts. The installing LLM authors the initial brief from the owner's request
|
|
265
269
|
and known application context; Ez stores it without trying to infer the product
|
|
266
|
-
itself. Installed plugin
|
|
267
|
-
`ez tools list --details
|
|
270
|
+
itself. Installed plugin aliases, purposes, skills and examples come from
|
|
271
|
+
`ez tools list --details`; no tool inventory file needs maintenance.
|
|
268
272
|
Credentials and control state stay outside the mind. Isolated agents use relay
|
|
269
273
|
mounts as the filesystem boundary. Host-capable file separation is not OS
|
|
270
274
|
isolation against a process running as the same user.
|
package/bin/ez
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createConnection } from 'node:net';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { fstatSync } from 'node:fs';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { installedNativeHelp } from '../src/plugins/native-tasks.mjs';
|
|
6
7
|
|
|
7
8
|
const MAX_STDIN_BYTES = 1024 * 1024;
|
|
9
|
+
const SOCKET_STDIN_IDLE_MS = 1000;
|
|
8
10
|
const RUN_ID = /^[A-Za-z0-9_-]{1,160}$/;
|
|
9
11
|
const socketPath = process.env.EZ_PLUGIN_BROKER_SOCKET?.trim();
|
|
10
12
|
const runId = process.env.EZ_RUN_ID?.trim();
|
|
@@ -50,18 +52,25 @@ const request = (payload, signal = undefined) => new Promise((resolve, reject) =
|
|
|
50
52
|
socket.on('connect', () => socket.write(JSON.stringify(payload) + '\n'));
|
|
51
53
|
});
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
// Pipes and files always end, so read them to EOF. A socket is how Node-based
|
|
56
|
+
// parents (native engine shells, agent scripts) wire child stdio, and they
|
|
57
|
+
// often pass it on without ever closing it. Waiting for that EOF hung calls
|
|
58
|
+
// before the broker was contacted, so a socket that stays silent means no input.
|
|
59
|
+
const readStdin = () => new Promise((resolve, reject) => {
|
|
60
|
+
if (process.stdin.isTTY) return resolve('');
|
|
55
61
|
const chunks = [];
|
|
56
62
|
let bytes = 0;
|
|
57
|
-
|
|
63
|
+
const idle = fstatSync(0).isSocket() ? setTimeout(() => { process.stdin.destroy(); resolve(''); }, SOCKET_STDIN_IDLE_MS) : undefined;
|
|
64
|
+
process.stdin.on('data', chunk => {
|
|
65
|
+
clearTimeout(idle);
|
|
58
66
|
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
59
67
|
bytes += value.length;
|
|
60
|
-
if (bytes > MAX_STDIN_BYTES)
|
|
68
|
+
if (bytes > MAX_STDIN_BYTES) { process.stdin.destroy(); return reject(Error('plugin stdin exceeds the broker limit')); }
|
|
61
69
|
chunks.push(value);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
};
|
|
70
|
+
});
|
|
71
|
+
process.stdin.once('end', () => { clearTimeout(idle); resolve(Buffer.concat(chunks).toString('utf8')); });
|
|
72
|
+
process.stdin.once('error', error => { clearTimeout(idle); reject(error); });
|
|
73
|
+
});
|
|
65
74
|
|
|
66
75
|
const invoke = async (args, stdin) => {
|
|
67
76
|
if (!runId || !RUN_ID.test(runId)) throw Error('EZ_RUN_ID is required for an isolated plugin command');
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
`menu.ts` owns Telegram controls; `ai.ts` projects installed-client metadata.
|
|
4
4
|
`ControlStore` stores saved presets, the next-conversation default and current choice.
|
|
5
5
|
No provider SDK, model fallback, history migration, or second execution loop.
|
|
6
|
+
A missing catalog entry rejects selection without changing the conversation.
|
|
7
|
+
An unsupported effort reports the exact installed model's advertised levels.
|
|
8
|
+
Missing selectable models do not establish that the active native session is
|
|
9
|
+
unavailable, and editing a scheduled task does not change the chat selection.
|
|
6
10
|
|
|
7
11
|
At intake each journal entry receives an immutable preset and conversation ID.
|
|
8
12
|
Batches cannot cross that boundary. The run copies that choice and invokes the
|
package/docs/docker-runtime.md
CHANGED
|
@@ -141,6 +141,18 @@ An interrupted Telegram poller keeps authorized work and in-flight deliveries
|
|
|
141
141
|
alive while it retries intake. Run and delivery records are relay memory: a
|
|
142
142
|
relay process restart drops in-flight work by design, Telegram redelivery is the
|
|
143
143
|
intake replay path, and uncertain sends report unknown instead of success.
|
|
144
|
+
Host-capable agents are the exception for running work: their native CLIs run under
|
|
145
|
+
the host transport, so a graceful relay stop (Compose recreate, Docker restart)
|
|
146
|
+
writes those running runs to private `control/run-handoff.json` instead of
|
|
147
|
+
cancelling them. The next relay adopts each one, keeps its concurrency slot and
|
|
148
|
+
schedule hold, accepts its messages and records its exit status; it never
|
|
149
|
+
relaunches a run. A run whose outcome is unknown (no request, claim or result
|
|
150
|
+
record, or an adoption failure) is recorded failed and interrupted, which holds
|
|
151
|
+
its schedule until an explicit edit; the host is asked to cancel it. A request
|
|
152
|
+
the host had not claimed before the relay stopped is failed by the host without
|
|
153
|
+
starting. Records older than 24 hours are ignored, and the file is removed on
|
|
154
|
+
every start. Queued runs and deliveries are still dropped, and messages a run
|
|
155
|
+
sends while no relay is up fail and need a retry.
|
|
144
156
|
A `409 Conflict` still requires the operator to stop the competing poller; it
|
|
145
157
|
remains unhealthy after the startup grace. Pending and uncertain deliveries keep
|
|
146
158
|
their existing receipt semantics; executor stdout is not replayed as a reply.
|
|
@@ -196,8 +208,12 @@ The image pins modern Docker Compose, including its standalone executable, so
|
|
|
196
208
|
existing operator-created volumes retain their data and driver bindings.
|
|
197
209
|
|
|
198
210
|
The relay-side `ez` client sends only declared aliases, literal argument arrays,
|
|
199
|
-
bounded stdin, and the active run ID to the broker.
|
|
200
|
-
|
|
211
|
+
bounded stdin, and the active run ID to the broker. It reads a piped or
|
|
212
|
+
redirected stdin to EOF. A socket stdin, which native engine shells and Node
|
|
213
|
+
child processes often leave open, counts as no input unless data arrives within
|
|
214
|
+
one second; write and close it immediately when a command needs input. The
|
|
215
|
+
broker re-reads owner authority and the registry before every call, pins the
|
|
216
|
+
discovered revision, and
|
|
201
217
|
passes only the installed plugin's `run.application.context.plugins[plugin-id]`
|
|
202
218
|
object as `EZ_PLUGIN_CONTEXT`. Missing, revoked, cross-agent, expired, or reused
|
|
203
219
|
admissions fail closed. Timeouts, cancellation and bounded output produce a
|
package/docs/plugin-guide.md
CHANGED
|
@@ -57,14 +57,14 @@ At minimum, provide:
|
|
|
57
57
|
machine-readable commands, stable exit codes, and readback for writes.
|
|
58
58
|
|
|
59
59
|
The manager snapshots the package according to `package.json.files` plus the
|
|
60
|
-
required manifests and Docker inputs. `ez tools list --details`
|
|
60
|
+
required manifests and Docker inputs. `ez tools list --details` prints aliases, purpose, skill paths, one example call
|
|
61
|
+
(the optional manifest `example`, else `<alias> --help`) and
|
|
61
62
|
`skillReads`: literal command argument arrays for reading each installed skill.
|
|
62
63
|
Use `ez tools skill <plugin> <declared-path>` to return its text and pinned
|
|
63
64
|
revision. The isolated executor cannot open private host package paths directly;
|
|
64
65
|
the authorized broker reads only manifest-declared regular skill files, without
|
|
65
66
|
copying them into the agent workspace. Skill reads reject symlinks and files over
|
|
66
|
-
64 KiB.
|
|
67
|
-
usage snippet or per-turn instruction is needed for this read contract.
|
|
67
|
+
64 KiB. No AGENTS.md text or per-turn instruction is needed for this read contract.
|
|
68
68
|
|
|
69
69
|
Keep one canonical checkout; do not keep
|
|
70
70
|
permanent packaged copies or a second registry. See the full
|
package/docs/plugins.md
CHANGED
|
@@ -27,8 +27,7 @@ node /absolute/ezenciel_agents/bin/ezenciel-agents-tools.mjs init \
|
|
|
27
27
|
Without `--catalog`, init loads an empty packaged catalog. Keep it empty for
|
|
28
28
|
initial main onboarding; no sibling repository, broker or provider account is
|
|
29
29
|
needed. Finish owner pairing and verify an actual Telegram agent reply first.
|
|
30
|
-
Init creates a private registry and `tools/bin/ez
|
|
31
|
-
managed AGENTS.md registry locator, and binds the matching host executor to that bin
|
|
30
|
+
Init creates a private registry and `tools/bin/ez` and binds the matching host executor to that bin
|
|
32
31
|
folder. Native binaries are linked through; the package's own `bin/ez` is replaced
|
|
33
32
|
by that bound launcher, while an unrelated existing `ez` collision fails.
|
|
34
33
|
Both host-bound and isolated `ez --help` list installed native scheduling and delivery helpers
|
|
@@ -307,8 +306,10 @@ never a source of network attachment authority.
|
|
|
307
306
|
|
|
308
307
|
Commands run in one-shot client containers against their own service's volumes;
|
|
309
308
|
stdin, stdout, stderr, literal arguments and exit codes are preserved. SIGINT/
|
|
310
|
-
SIGTERM cancel the Docker call and remove its unique client container
|
|
311
|
-
|
|
309
|
+
SIGTERM cancel the Docker call and remove its unique client container; a Compose
|
|
310
|
+
exit with a non-zero status also removes it, so a call that stopped between
|
|
311
|
+
create and start never remains `Created`. The manager never retries a provider
|
|
312
|
+
operation. Service startup/restart is explicit; commands
|
|
312
313
|
never implicitly start a stopped provider service. Plugin data does not enter the
|
|
313
314
|
relay. The manager sends only a whitelist of Docker client environment variables.
|
|
314
315
|
|
|
@@ -412,10 +413,12 @@ QMD runtimes or edit installed Compose/package files to bypass missing support.
|
|
|
412
413
|
|
|
413
414
|
## Generated capability discovery
|
|
414
415
|
|
|
415
|
-
`ez tools list --details` generates a
|
|
416
|
-
`ez-plugin.json` manifests
|
|
417
|
-
|
|
418
|
-
|
|
416
|
+
`ez tools list --details` generates a self-sufficient index directly from
|
|
417
|
+
installed `ez-plugin.json` manifests. Per plugin it prints `aliases`, a one-line
|
|
418
|
+
`purpose` (the manifest `description`, at most 200 characters), absolute `skills`
|
|
419
|
+
paths and one `example` call. The optional manifest `example` is one line of at
|
|
420
|
+
most 160 characters starting with a declared command alias; without it the
|
|
421
|
+
example is `ez <first alias> --help`. Full instructions stay in the skill. Installation, replacement and removal are reflected
|
|
419
422
|
on the next read, without hooks, LLM calls or a cached inventory file.
|
|
420
423
|
|
|
421
424
|
The additive `skillReads` field supplies literal argument arrays for
|
|
@@ -424,8 +427,8 @@ installed plugin revision, declared path and skill content. Use it in isolated
|
|
|
424
427
|
agents, where host package paths are private. It reads only the installed
|
|
425
428
|
manifest's skills; arbitrary files, symlinks and files over 64 KiB are rejected.
|
|
426
429
|
|
|
427
|
-
`ez tools list` retains its alias mapping for existing clients.
|
|
428
|
-
|
|
430
|
+
`ez tools list` retains its alias mapping for existing clients. The workspace
|
|
431
|
+
seed writes one static line pointing here; nothing else is written. New workspaces do not seed
|
|
429
432
|
TOOLS.md, SOUL.md, USER.md, or an `inbox/`/`work/` layout; upgrades preserve
|
|
430
433
|
legacy notes without rewriting them. Keep owner/account policies in agent
|
|
431
434
|
instructions or linked policy files, separate from plugin metadata.
|
package/docs/scheduling.md
CHANGED
|
@@ -57,7 +57,7 @@ available through `ezenciel-agents-schedule context`.
|
|
|
57
57
|
|
|
58
58
|
## Ordered setup fallback
|
|
59
59
|
|
|
60
|
-
An agent task may list
|
|
60
|
+
An agent task may list ordered fallback setups after its saved one, for
|
|
61
61
|
example `claude · opus → claude · sonnet → codex · gpt-6-sol`:
|
|
62
62
|
|
|
63
63
|
```sh
|
|
@@ -68,30 +68,58 @@ ezenciel-agents-schedule create --name Daily --cli claude --model opus \
|
|
|
68
68
|
|
|
69
69
|
`edit` keeps the saved fallbacks unless `--fallback` replaces them or
|
|
70
70
|
`--clear-fallbacks` removes them. Setups must be distinct and each needs a model.
|
|
71
|
+
There is no fixed list-length limit. Each setup captures its own supported CLI,
|
|
72
|
+
model, effort and optional provider; the installed native client owns valid model
|
|
73
|
+
and effort values. The existing failure rules below apply to every setup.
|
|
74
|
+
Each fallback may include `auth-profile=NAME` to select its own owner-provisioned
|
|
75
|
+
Codex or Claude account. Omitting it selects the default login, independently of
|
|
76
|
+
the primary setup. For four separate Claude accounts, provision and log into
|
|
77
|
+
`claude2`, `claude3` and `claude4`, then use:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
ezenciel-agents-schedule create --name Daily --cli claude --model opus \
|
|
81
|
+
--cron '0 9 * * *' --timezone Asia/Dubai --text 'Prepare the daily report.' \
|
|
82
|
+
--fallback cli=claude,model=opus,auth-profile=claude2 \
|
|
83
|
+
--fallback cli=claude,model=opus,auth-profile=claude3 \
|
|
84
|
+
--fallback cli=claude,model=opus,auth-profile=claude4
|
|
85
|
+
```
|
|
71
86
|
|
|
72
87
|
Core advances to the next setup only with typed evidence that nothing ran: the
|
|
73
88
|
CLI is not installed or executable, or the provider rejected the turn before any
|
|
74
89
|
model output, tool or hook item (quota/rate limit, billing, overload, server
|
|
75
|
-
error, unavailable model or bad request)
|
|
76
|
-
|
|
90
|
+
error, unavailable model or bad request), including a missing, expired or revoked
|
|
91
|
+
login (typed authentication failure or HTTP401). Native API-error metadata does
|
|
92
|
+
not count as model/tool work. It stops, without replay, on access
|
|
93
|
+
denial (HTTP403, account or organization restriction), cancellation, relay shutdown,
|
|
77
94
|
a schedule edit, a policy or unknown error, an interrupted transport, or any
|
|
78
|
-
failure after work began. Evidence comes from structured events only: Codex
|
|
95
|
+
other failure after work began. Evidence comes from structured events only: Codex
|
|
79
96
|
native-session `codexErrorInfo` and Claude `stream-json` error fields (scheduled
|
|
80
97
|
Claude runs use `--output-format stream-json`; the host forwards only a compact
|
|
81
98
|
startup summary). Other engines advance only when their CLI is unavailable.
|
|
82
99
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
The one failure after work began that continues is a Claude usage limit the run
|
|
101
|
+
ended on (for example "You've hit your session limit"): a typed quota rejection
|
|
102
|
+
with no model, tool or hook activity after it. A native session cannot be resumed
|
|
103
|
+
under another login's configuration home, so the next eligible setup starts a fresh
|
|
104
|
+
session with the saved prompt plus one factual line naming the run, the stopped
|
|
105
|
+
setup, its native session and reset time, and that its work may be partly applied.
|
|
106
|
+
The agent reconciles from its own records; core replays nothing else.
|
|
107
|
+
|
|
108
|
+
A login or quota rejection skips the remaining setups in the same scope: the native
|
|
109
|
+
client and login profile (`claude`, `codex` including desktop and custom Codex
|
|
110
|
+
providers), `grok`, or the upstream provider for OpenCode/Pi models. A named
|
|
111
|
+
profile selects a separate owner-provisioned account and is eligible for its own
|
|
112
|
+
attempt; changing only the model in the same profile does not reset its scope.
|
|
113
|
+
Do not provision aliases of the same account to obtain more allowance. Ez does
|
|
114
|
+
not infer account independence from credentials or promise that a shared
|
|
115
|
+
organization's allowance is separate. Each setup runs at most once per occurrence.
|
|
88
116
|
|
|
89
117
|
Every attempt keeps the same run ID, occurrence, schedule revision, workspace,
|
|
90
118
|
delivery binding and scheduled concurrency slot, in a fresh native session.
|
|
91
119
|
`run RUN_ID` shows `attempts`: each setup, quota scope, outcome, failure category
|
|
92
|
-
(`cli-unavailable`, `quota`, `provider-rejected`, `access-denied`,
|
|
93
|
-
`after-work-began`, `uncertain`, or `same-quota-scope`
|
|
94
|
-
code
|
|
120
|
+
(`cli-unavailable`, `login-unavailable`, `quota`, `provider-rejected`, `access-denied`,
|
|
121
|
+
`quota-after-work-began`, `after-work-began`, `uncertain`, or `same-login-scope`/`same-quota-scope`
|
|
122
|
+
for a skipped setup), exit code, reported reset time and the session ID Ez assigned. If every setup is unavailable the run fails with
|
|
95
123
|
reason `setups-unavailable`, a summary of each setup, and an owner notice on
|
|
96
124
|
Telegram-bound tasks. Core never purchases credits, enables overage or consumes
|
|
97
125
|
reset credits; the next regular occurrence runs normally.
|
|
@@ -225,10 +253,13 @@ queued occurrence; already-running work continues until explicitly cancelled.
|
|
|
225
253
|
|
|
226
254
|
`/stop` stops all active work; `cancel RUN_ID` stops one background task. `/cancel`
|
|
227
255
|
clears queued work. Pause/remove a recurring schedule to prevent future runs.
|
|
228
|
-
Stopping the relay also stops its workers
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
256
|
+
Stopping the relay also stops its workers, except on host-capable agents: a
|
|
257
|
+
graceful relay stop hands running host-transport runs to the next relay, which
|
|
258
|
+
adopts them without relaunching (see [Docker runtime](docker-runtime.md)). A
|
|
259
|
+
request the host had not yet claimed when the relay stopped is failed by the host
|
|
260
|
+
without starting. Run and delivery records are otherwise relay memory: a process
|
|
261
|
+
restart drops in-flight runs and queued deliveries by design, so the next
|
|
262
|
+
eligible occurrence dispatches normally and nothing is replayed from disk. Status labels failed runs as history and shows recent reasons; new failures retain their exit code or interruption cause. Typing indicators stop after 30 seconds even when work continues. A failed run that cannot prove whether an external side effect happened stays visible until the agent inspects the evidence and explicitly edits the schedule. Inspect the task's files, native session and delivery
|
|
232
263
|
receipts before deciding whether to resume. A clock cannot reconstruct an
|
|
233
264
|
in-flight process or prove whether an external side effect happened.
|
|
234
265
|
|
|
@@ -310,7 +341,13 @@ The prompt controls diagnosis, authorized recovery and quiet notification behavi
|
|
|
310
341
|
The Telegram Scheduled tasks menu lists enabled schedules that still have a pending
|
|
311
342
|
occurrence or a queued/running occurrence. Finished one-time tasks, paused
|
|
312
343
|
schedules and revisions stopped for review are hidden. Each entry shows the
|
|
313
|
-
|
|
344
|
+
frequency with the cron timezone, the saved primary and fallback setups in order
|
|
345
|
+
(CLI, login profile, model and effort), next occurrence in UTC (or queued/running state),
|
|
314
346
|
and the first sentence of its saved invocation
|
|
315
347
|
prompt, limited to 140 characters. This is a read-only view; history and full
|
|
316
|
-
prompts remain available through the scheduling CLI.
|
|
348
|
+
prompts remain available through the scheduling CLI. `/tasks N` shows the same
|
|
349
|
+
frequency and execution path with the saved instructions. Common minute/hourly,
|
|
350
|
+
daily and weekday rules have plain-language labels; other calendar rules show
|
|
351
|
+
the exact cron expression and timezone. Interval rules show their fixed period,
|
|
352
|
+
and one-time tasks show `Once`. This describes the schedule, not a guarantee of
|
|
353
|
+
work when admission/preflight conditions are unmet.
|
package/docs/setup.md
CHANGED
|
@@ -261,8 +261,8 @@ node /absolute/ezenciel_agents/bin/ezenciel-agents-tools.mjs init \
|
|
|
261
261
|
|
|
262
262
|
This binds a private `ez` and preserves native command access. The default catalog
|
|
263
263
|
is empty. Do not supply a plugin catalog during first-time main onboarding.
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
The seeded
|
|
265
|
+
AGENTS.md already points at `ez tools list --details`. Verify `tools/bin/ez plugins
|
|
266
266
|
available` before the first agent turn. Also execute the agent-bound
|
|
267
267
|
`ezenciel-agents-message --help` through the selected CLI sandbox and verify
|
|
268
268
|
its actual tool output, so absent launchers or blocked execution are detected
|
package/docs/standalone-cli.md
CHANGED
|
@@ -22,14 +22,14 @@ node /absolute/package/bin/ezenciel-agents-tools.mjs init --standalone \
|
|
|
22
22
|
/absolute/private/company-tools/bin/ez plugins list
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Init starts nothing, uses an empty catalog by default
|
|
26
|
-
workspace
|
|
25
|
+
Init starts nothing, uses an empty catalog by default and never writes
|
|
26
|
+
workspace instructions. A registry
|
|
27
27
|
cannot be replaced by rerunning init. Keep the package at its original path:
|
|
28
28
|
the launcher imports it. Status reports `main: null` without a relay binding;
|
|
29
29
|
automated software upgrades currently require a relay deployment.
|
|
30
30
|
|
|
31
|
-
Use `ez tools list --details` for
|
|
32
|
-
Add
|
|
31
|
+
Use `ez tools list --details` for each installed plugin's aliases, purpose, skill paths and one example call. Read only the relevant skill.
|
|
32
|
+
Add "Tools: run `ez tools list --details`; each plugin's `--help` gives usage." to the project's existing instructions, with `ez` resolving to the returned launcher.
|
|
33
33
|
Use the absolute launcher, or prepend its bin directory to that session's PATH.
|
|
34
34
|
Never overwrite another global `ez`; it may belong to a different installation.
|
|
35
35
|
A standalone company registry remains explicit even when invoked from a different
|
package/docs/upgrades.md
CHANGED
|
@@ -255,19 +255,15 @@ handbook from workspace `AGENTS.md` (and existing `AGENTS.override.md`).
|
|
|
255
255
|
Personal text outside that block is preserved byte-for-byte. Do not reinsert
|
|
256
256
|
coding, KISS, contribution or repair loops into operating minds.
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Inventory and
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Codex, Grok and OpenCode discover workspace instructions natively. Claude
|
|
269
|
-
receives the native `--append-system-prompt-file` binding to `AGENTS.md`, alongside
|
|
270
|
-
its own normal instructions. Fresh and resumed owner input is literal; ez does
|
|
258
|
+
Ez writes no managed guidance after the initial workspace seed. Update binding
|
|
259
|
+
(init with a host config, `enable-updates`, upgrade and rollback) replaces a former
|
|
260
|
+
`<!-- ez tools -->` footer once with the seeded static line
|
|
261
|
+
"Tools: run `ez tools list --details`; each plugin's `--help` gives usage." and
|
|
262
|
+
otherwise leaves `AGENTS.md` untouched. Malformed markers and symlinks fail visibly
|
|
263
|
+
rather than overwriting personal work. Inventory and usage are generated on read.
|
|
264
|
+
|
|
265
|
+
Codex, Grok, OpenCode and Claude Code discover workspace `AGENTS.md` natively
|
|
266
|
+
(Claude Code loads it when the workspace has no `CLAUDE.md`). Fresh and resumed owner input is literal; ez does
|
|
271
267
|
not surround it with policies, tool recipes, repair instructions or history.
|
|
272
268
|
The native engine owns instruction loading and its context/token overhead.
|
|
273
269
|
Already running sessions retain their current context until native reload.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jc_stack/ez-agents",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.50",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A lightweight foundation for persistent business AI assistants using existing AI harnesses, workspaces and plugins.",
|
package/src/agent-guidance.ts
CHANGED
|
@@ -5,8 +5,8 @@ import path from 'node:path'
|
|
|
5
5
|
const start = '<!-- ez shared guidance: begin -->'
|
|
6
6
|
const end = '<!-- ez shared guidance: end -->'
|
|
7
7
|
|
|
8
|
-
// Drop the obsolete package-owned handbook. Discovery
|
|
9
|
-
// ez tools
|
|
8
|
+
// Drop the obsolete package-owned handbook. Discovery is seeded once and
|
|
9
|
+
// generated by `ez tools list --details`. Personal text outside the markers stays byte-for-byte.
|
|
10
10
|
export async function installAgentGuidance(workspace: string): Promise<void> {
|
|
11
11
|
for (const name of ['AGENTS.md', 'AGENTS.override.md']) {
|
|
12
12
|
const file = path.join(workspace, name)
|
package/src/ai.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { AUTH_PROFILE_CLIS, claudeAuthEnvironment, cliHome, isAuthProfile, listA
|
|
|
12
12
|
export type AiPreset = { id: string; name: string; cli: string; provider?: string; authProfile?: string; model?: string; effort?: string }
|
|
13
13
|
// Optional ordered fallbacks are tried only for a scheduled occurrence that never started work.
|
|
14
14
|
export type ExecutionChoice = { sessionId: string; preset: AiPreset; fallbacks?: AiPreset[] }
|
|
15
|
-
export const MAX_FALLBACKS = 3
|
|
16
15
|
export type ModelChoice = { cli: string; provider?: string; authProfile?: string; model?: string; name: string; efforts: string[] }
|
|
17
16
|
type Engine = { cli?: string; authProfile?: string }
|
|
18
17
|
// A native conversation belongs to one client and one credential home.
|
|
@@ -34,7 +33,7 @@ export const isPreset = (p: unknown): p is AiPreset => {
|
|
|
34
33
|
export const isExecutionChoice = (v: unknown): v is ExecutionChoice => {
|
|
35
34
|
const c = v as ExecutionChoice | undefined
|
|
36
35
|
return Boolean(c && /^[0-9a-f-]{36}$/i.test(c.sessionId) && isPreset(c.preset) &&
|
|
37
|
-
(c.fallbacks === undefined || (Array.isArray(c.fallbacks) && c.fallbacks.length > 0 && c.fallbacks.
|
|
36
|
+
(c.fallbacks === undefined || (Array.isArray(c.fallbacks) && c.fallbacks.length > 0 && c.fallbacks.every(isPreset))))
|
|
38
37
|
}
|
|
39
38
|
export const presetLabel = (p: AiPreset) => `${p.cli}${p.authProfile ? `@${p.authProfile}` : ''}${p.provider ? ` (${p.provider})` : ''} · ${p.model || 'client default'} · ${p.effort || 'default effort'}`
|
|
40
39
|
// OpenCode encodes its provider in the native provider/model identifier.
|
|
@@ -264,6 +263,21 @@ export const opencodeProviderAllowlist = (env: NodeJS.ProcessEnv = process.env):
|
|
|
264
263
|
return validateOpencodeProviders([...new Set(raw.split(',').map((p) => p.trim()).filter(Boolean))])
|
|
265
264
|
}
|
|
266
265
|
|
|
266
|
+
// Shared by native menus and application controls; a rejected choice never mutates state.
|
|
267
|
+
export const catalogSelection = (p: {cli?: unknown; provider?: unknown; authProfile?: unknown; model?: unknown; effort?: unknown}, catalog: ModelChoice[]): ModelChoice => {
|
|
268
|
+
const candidate = {...p, id: 'selection', name: 'Selection'}
|
|
269
|
+
if (!isPreset(candidate)) throw new Error('Invalid AI selection fields')
|
|
270
|
+
const model = catalog.find(m => sameEngine(m, candidate) && m.provider === candidate.provider && m.model === candidate.model)
|
|
271
|
+
if (!model) {
|
|
272
|
+
if (!catalog.some(m => sameEngine(m, candidate)))
|
|
273
|
+
throw new Error(`No models advertised for ${candidate.cli}${candidate.authProfile ? `@${candidate.authProfile}` : ''} in the installed client catalog; current selection unchanged.`)
|
|
274
|
+
throw new Error(`Model ${candidate.model ?? '(client default)'} is not in the installed client catalog for ${candidate.cli}; current selection unchanged.`)
|
|
275
|
+
}
|
|
276
|
+
if (candidate.effort !== undefined && !model.efforts.includes(candidate.effort))
|
|
277
|
+
throw new Error(`Effort ${candidate.effort} is not supported for ${candidate.cli} ${candidate.model ?? '(client default)'}. Supported efforts: ${model.efforts.join(', ') || 'none (omit effort)'}; current selection unchanged.`)
|
|
278
|
+
return model
|
|
279
|
+
}
|
|
280
|
+
|
|
267
281
|
export const validateSelection = async (p: AiPreset, catalog: ModelChoice[], available = installed): Promise<void> => {
|
|
268
282
|
assertEffort(p.effort, p.model, p.cli)
|
|
269
283
|
if (!isPreset(p)) throw new Error('This CLI is not installed.')
|
|
@@ -273,9 +287,7 @@ export const validateSelection = async (p: AiPreset, catalog: ModelChoice[], ava
|
|
|
273
287
|
if (!catalog.some((m) => sameEngine(m, p))) throw new Error('This auth profile is not provisioned for the selected CLI.')
|
|
274
288
|
return
|
|
275
289
|
}
|
|
276
|
-
|
|
277
|
-
if (!model || (p.effort && !model.efforts.includes(p.effort)))
|
|
278
|
-
throw new Error('This model/effort is not in the installed client catalog. Refresh the client and try again; no fallback was selected.')
|
|
290
|
+
catalogSelection(p, catalog)
|
|
279
291
|
}
|
|
280
292
|
|
|
281
293
|
export type AuthProfileStatus = { cli: string; authProfile: string | null; provisioned: boolean; sharesHostLogin: boolean
|
|
@@ -2,11 +2,11 @@ import { createServer, type Server, type IncomingMessage, type ServerResponse }
|
|
|
2
2
|
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto'
|
|
3
3
|
import { mkdir, readFile, writeFile, rename, open, unlink, stat, chown } from 'node:fs/promises'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
|
-
import { ControlStore, sessionTitle, telegramOwner, type ControlGuard, type Owner, sameOwner, validOwner, ownerId, ownerEpoch } from './control-state.js'
|
|
5
|
+
import { ControlStore, sessionTitle, telegramOwner, type ControlGuard, type Owner, type SessionState, sameOwner, validOwner, ownerId, ownerEpoch } from './control-state.js'
|
|
6
6
|
import { RunStore, type RunRecord, type OutboxItem } from './runs.js'
|
|
7
7
|
import { ownsRun } from './identity.js'
|
|
8
8
|
import { applicationId, validApplicationOrigin } from './application-origin.js'
|
|
9
|
-
import { isPreset, sameChoice,
|
|
9
|
+
import { isPreset, sameChoice, catalogSelection, type AiPreset, type ModelChoice } from './ai.js'
|
|
10
10
|
import { MAX_INCOMING_ATTACHMENT_BYTES, stageChatAttachment } from './files.js'
|
|
11
11
|
import { assertEffort } from './model-policy.js'
|
|
12
12
|
import { ApprovalStore } from './approval.js'
|
|
@@ -97,6 +97,8 @@ export class ApplicationChannel {
|
|
|
97
97
|
constructor(private options: {
|
|
98
98
|
controlDir: string; workspace?: string; isolation?: string; initial: AiPreset
|
|
99
99
|
wake: () => void
|
|
100
|
+
// Queues the replaced conversation's silent close turn (session-close.ts).
|
|
101
|
+
closeSession?: (session: SessionState | null) => Promise<void>
|
|
100
102
|
cancel: (id: string) => Promise<void>
|
|
101
103
|
speech?: (text: string, language: 'en' | 'es') => Promise<{ buffer: Buffer; mimeType: string }>
|
|
102
104
|
createTelegramPairing?: (bindingId: string, owner: Owner) => Promise<{ connected: true } | { connected: false; url: string; expiresAt: string }>
|
|
@@ -155,7 +157,7 @@ export class ApplicationChannel {
|
|
|
155
157
|
const owner = await this.sharedBinding(bindingId)
|
|
156
158
|
const controls = this.options.aiControls
|
|
157
159
|
if (!controls) throw new Error('Application controls unavailable')
|
|
158
|
-
const value = input as {action?: unknown; expectedSession?: unknown; sessionId?: unknown; presetId?: unknown; cli?: unknown; provider?: unknown; model?: unknown; effort?: unknown}
|
|
160
|
+
const value = input as {action?: unknown; expectedSession?: unknown; sessionId?: unknown; presetId?: unknown; cli?: unknown; provider?: unknown; authProfile?: unknown; model?: unknown; effort?: unknown}
|
|
159
161
|
if (!value || !['new','switch','select','model'].includes(String(value.action)) ||
|
|
160
162
|
!(value.expectedSession === null || (typeof value.expectedSession === 'string' && /^[a-f0-9-]{36}$/.test(value.expectedSession)))) throw new Error('Invalid application control request')
|
|
161
163
|
const fields = {new:[],switch:['sessionId'],select:['presetId'],model:['cli','provider','authProfile','model','effort']}[value.action as 'new'|'switch'|'select'|'model']!
|
|
@@ -168,7 +170,11 @@ export class ApplicationChannel {
|
|
|
168
170
|
const live = (await this.bindings.list()).find(item=>item.bindingId===bindingId)
|
|
169
171
|
if (!live?.shareTelegram || !sameOwner(owner, live.owner)) throw new Error('Application authority revoked')
|
|
170
172
|
}}
|
|
171
|
-
if (value.action === 'new')
|
|
173
|
+
if (value.action === 'new') {
|
|
174
|
+
const previous = await control.getActiveSession()
|
|
175
|
+
await control.resetSession(expected, guard)
|
|
176
|
+
await this.options.closeSession?.(previous)
|
|
177
|
+
}
|
|
172
178
|
if (value.action === 'switch') {
|
|
173
179
|
if (typeof value.sessionId !== 'string') throw new Error('Invalid application conversation')
|
|
174
180
|
// switchSession enforces the same visibility/engine restrictions as /chats.
|
|
@@ -181,8 +187,7 @@ export class ApplicationChannel {
|
|
|
181
187
|
await controls.select(preset, expected, guard)
|
|
182
188
|
}
|
|
183
189
|
if (value.action === 'model') {
|
|
184
|
-
const model = (await controls.catalog())
|
|
185
|
-
if (!model || (value.effort !== undefined && (typeof value.effort !== 'string' || !model.efforts.includes(value.effort)))) throw new Error('Invalid application model selection')
|
|
190
|
+
const model = catalogSelection(value, await controls.catalog())
|
|
186
191
|
await this.sharedBinding(bindingId)
|
|
187
192
|
const preset = await controls.saveSelection(model, value.effort as string | undefined, guard)
|
|
188
193
|
await this.sharedBinding(bindingId)
|
|
@@ -231,8 +236,7 @@ export class ApplicationChannel {
|
|
|
231
236
|
await controls.validate(preset)
|
|
232
237
|
}
|
|
233
238
|
if (value.action==='model') {
|
|
234
|
-
const model = (await controls.catalog())
|
|
235
|
-
if (!model || (value.effort!==undefined && (typeof value.effort!=='string' || !model.efforts.includes(value.effort)))) throw new Error('Invalid application model selection')
|
|
239
|
+
const model = catalogSelection(value, await controls.catalog())
|
|
236
240
|
preset = await controls.saveSelection(model,value.effort as string|undefined,guard)
|
|
237
241
|
}
|
|
238
242
|
await control.changeApplicationSession(hashedScope,guard,preset)
|
package/src/executor.ts
CHANGED
|
@@ -500,6 +500,15 @@ export const startExecutorJob = async (
|
|
|
500
500
|
|
|
501
501
|
export const startGrokJob = startExecutorJob
|
|
502
502
|
|
|
503
|
+
// Watch a host-transport run a previous relay submitted; it is never resubmitted.
|
|
504
|
+
export const attachHostJob = async (controlDir: string, runId: string): Promise<ChildProcess> => {
|
|
505
|
+
const invocation = executorInvocation(process.execPath, ['--import', fileURLToPath(new URL('../node_modules/tsx/dist/loader.mjs', import.meta.url)),
|
|
506
|
+
fileURLToPath(new URL('./host-executor-client.ts', import.meta.url)), controlDir, runId, '--attach'])
|
|
507
|
+
const child = spawn(invocation.command, invocation.args, { env: executorEnvironment(), stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' })
|
|
508
|
+
await new Promise<void>((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject) })
|
|
509
|
+
return child
|
|
510
|
+
}
|
|
511
|
+
|
|
503
512
|
// Spawn a verified registered script with the executor identity, environment
|
|
504
513
|
// allowlist and workspace confinement. No shell, model, prompt or native session.
|
|
505
514
|
export const startScriptJob = async (
|