@jc_stack/ez-agents 0.1.0-beta.13 → 0.1.0-beta.18
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/.dockerignore +3 -0
- package/.env.example +15 -0
- package/AGENTS.md +6 -3
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +34 -4
- package/README.md +3 -0
- package/compose.yaml +8 -1
- package/docker/run.ts +1 -1
- package/docs/architecture/ai-selection.md +8 -0
- package/docs/architecture/authority-boundaries.md +24 -1
- package/docs/architecture/telegram-intake.md +1 -1
- package/docs/docker-runtime.md +35 -0
- package/docs/host-service.md +19 -0
- package/docs/pagerduty.md +42 -0
- package/docs/plugin-catalog.md +27 -10
- package/docs/plugin-contributions.md +9 -0
- package/docs/plugins.md +12 -1
- package/docs/releasing.md +20 -9
- package/docs/repair.md +41 -0
- package/docs/scheduling.md +30 -4
- package/docs/selective-monitoring.md +12 -4
- package/docs/setup.md +39 -0
- package/docs/trusted-publishing.md +140 -0
- package/docs/upgrades.md +24 -4
- package/package.json +6 -3
- package/scripts/generate-publish-caller.mjs +60 -0
- package/scripts/smoke-busy-reply.ts +58 -0
- package/scripts/trusted-beta.mjs +289 -0
- package/src/agent-guidance.ts +5 -0
- package/src/ai-cli.ts +2 -1
- package/src/ai.ts +15 -5
- package/src/client-defaults.ts +29 -13
- package/src/codex-session.ts +4 -2
- package/src/config.ts +29 -1
- package/src/control-state.ts +24 -7
- package/src/desktop-bridge.ts +8 -1
- package/src/event-sources.ts +2 -1
- package/src/execution-authority.ts +2 -1
- package/src/executor.ts +31 -6
- package/src/failure.ts +32 -0
- package/src/host-executor.ts +22 -13
- package/src/identity.ts +8 -3
- package/src/inbox.ts +7 -3
- package/src/index.ts +207 -79
- package/src/install-tools.mjs +2 -2
- package/src/menu.ts +6 -4
- package/src/model-policy.ts +15 -0
- package/src/owner.ts +3 -3
- package/src/pagerduty.ts +109 -0
- package/src/plugins/manager.mjs +47 -8
- package/src/plugins/shared.mjs +76 -0
- package/src/repair-policy.ts +13 -0
- package/src/reply-context.ts +67 -0
- package/src/reply-executor.ts +54 -0
- package/src/reply-mcp.ts +23 -0
- package/src/runs.ts +15 -4
- package/src/schedule-cli.ts +36 -7
- package/src/scheduler.ts +12 -3
- package/src/setup.ts +2 -1
- package/src/software-status.ts +5 -5
- package/src/task-cli.ts +3 -3
- package/src/task-executor.ts +7 -5
- package/src/tasks.ts +35 -17
- package/src/telegram-source.ts +94 -0
- package/src/updates/artifact.mjs +16 -0
- package/src/updates/binding.mjs +3 -1
- package/src/updates/control.mjs +4 -4
- package/src/updates/runtime.mjs +3 -1
- package/templates/agent/AGENTS.md +10 -2
- package/templates/agent/TOOLS.md +6 -0
- package/templates/agent-guidance.md +13 -0
- package/templates/failure-review.md +9 -0
- package/templates/maintainer-purpose.md +15 -0
- package/templates/updates.md +2 -2
- package/test/agent-guidance.test.ts +110 -0
- package/test/ai-cli.test.ts +7 -6
- package/test/ai.test.ts +41 -0
- package/test/busy-reply-relay.test.ts +41 -0
- package/test/client-defaults.test.ts +37 -5
- package/test/codex-context.test.ts +5 -2
- package/test/codex-session.test.ts +4 -2
- package/test/config.test.ts +29 -0
- package/test/executor.test.ts +11 -1
- package/test/failure.test.ts +250 -0
- package/test/group-owner.test.ts +36 -0
- package/test/host-executor.test.ts +38 -7
- package/test/intake-relay.test.ts +141 -4
- package/test/model-policy.test.ts +61 -0
- package/test/pagerduty.test.ts +104 -0
- package/test/plugin-manager.test.mjs +3 -2
- package/test/relay.test.ts +2 -2
- package/test/repair-policy.test.ts +23 -0
- package/test/reply.test.ts +131 -0
- package/test/schedule-cli.test.ts +8 -2
- package/test/shared-services.test.mjs +98 -0
- package/test/software-status.test.ts +5 -5
- package/test/task-native.test.ts +2 -2
- package/test/tasks.test.ts +14 -6
- package/test/telegram-source.test.ts +75 -0
- package/test/trusted-beta.test.mjs +224 -0
- package/test/updates.test.mjs +35 -3
package/docs/scheduling.md
CHANGED
|
@@ -32,6 +32,14 @@ the host changes zones. Nonexistent DST wall times are skipped; repeated wall
|
|
|
32
32
|
times fire once, at the earlier instant. Search is bounded to eight years.
|
|
33
33
|
Public-holiday calendars and arbitrary RRULE syntax are not implemented.
|
|
34
34
|
|
|
35
|
+
New tasks, including work deferred by a busy reply session, default to Codex
|
|
36
|
+
`gpt-5.6-terra` with `high` reasoning independently of the creating chat.
|
|
37
|
+
Use `--cli`, `--model`, and `--effort` to specify another choice; reasoning
|
|
38
|
+
explicitly selected above `high` is rejected for every model. Non-Codex adapters
|
|
39
|
+
inherit native effort when unset. Editing preserves the existing AI
|
|
40
|
+
choice unless those flags override it. Stored choices are checked again at
|
|
41
|
+
launch, including schedules saved before the cap.
|
|
42
|
+
|
|
35
43
|
## Execution and authority
|
|
36
44
|
|
|
37
45
|
The relay checks due work once per second. Each occurrence enters the durable
|
|
@@ -41,7 +49,7 @@ Instructions must include any needed context or source paths; full chat history
|
|
|
41
49
|
is not copied. Task folders remain for inspection and artifact delivery.
|
|
42
50
|
|
|
43
51
|
One writer runs per task directory. Up to four background tasks can run alongside
|
|
44
|
-
the
|
|
52
|
+
the main conversation. When a Codex owner message arrives while work is busy, a separate restricted session reads recent messages and run progress and answers through the normal outbox. It can queue requested work through the scheduler, but cannot run shell commands, access plugins, or edit the agent workspace. Only one reply session runs at a time and it releases its slot after a 60-second reply deadline; this deadline does not apply to writer jobs. Its context is a bounded snapshot, not a shared native transcript. Delivered parallel replies are included as historical context in the next normal conversation turn. Codex 0.153.4 and 0.154.0 are supported for this restricted adapter. Other versions fail closed pending tool-surface validation. A recurring schedule has at most one pending
|
|
45
53
|
or active occurrence. Agents should delegate long work with `create --now`, return
|
|
46
54
|
to chat, and inspect `runs` or task progress when asked. Native subagents can be
|
|
47
55
|
used inside the worker. Sharing provider profiles does not make concurrent CRM,
|
|
@@ -70,8 +78,8 @@ The foreground chat still uses `codex exec`. That invocation exits after one
|
|
|
70
78
|
requested turn even if a goal is active, so delegate persistent work to the
|
|
71
79
|
scheduler. Desktop and other executor goal lifecycles need separate validation.
|
|
72
80
|
|
|
73
|
-
The CLI binds jobs to the paired owner and
|
|
74
|
-
work retains that
|
|
81
|
+
The CLI binds jobs to the paired owner and the task AI choice. Queued/scheduled
|
|
82
|
+
work retains that choice after the chat switches AI. Revoking/re-pairing an
|
|
75
83
|
owner invalidates their old schedules, including re-pairing the same Telegram ID.
|
|
76
84
|
External event turns cannot use the scheduling CLI. Credentials still pass only
|
|
77
85
|
through the existing whitelist and installed host binding.
|
|
@@ -88,7 +96,7 @@ queued occurrence; already-running work continues until explicitly cancelled.
|
|
|
88
96
|
`/stop` stops all active work; `cancel RUN_ID` stops one background task. `/cancel`
|
|
89
97
|
clears queued work. Pause/remove a recurring schedule to prevent future runs.
|
|
90
98
|
Stopping the relay also stops its workers. A crashed or interrupted execution is
|
|
91
|
-
not automatically replayed. Runs found active at startup are marked failed with
|
|
99
|
+
not automatically replayed. 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. Runs found active at startup are marked failed with
|
|
92
100
|
`interrupted: true`; their schedule revision stays held until the agent inspects
|
|
93
101
|
the evidence and explicitly edits the schedule. Inspect the task's files, native session and delivery
|
|
94
102
|
receipts before deciding whether to resume. A clock cannot reconstruct an
|
|
@@ -125,3 +133,21 @@ minutes. Verify `finished.txt` and exactly one completion in Telegram. Separatel
|
|
|
125
133
|
exercise cancellation, downtime catch-up and an explicitly requested native goal
|
|
126
134
|
that needs more than one turn. Synthetic provider evidence does not prove real
|
|
127
135
|
Telegram delivery, and a sleep test does not prove native goal persistence.
|
|
136
|
+
|
|
137
|
+
Busy-chat regression probe (real Codex, synthetic Telegram):
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
pnpm exec tsx scripts/smoke-busy-reply.ts --transport
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The probe holds a writer on a shared workspace, asks an owner question through
|
|
144
|
+
the relay and host transport, and requires the restricted reply to complete
|
|
145
|
+
while the writer remains active. It sends no real Telegram messages.
|
|
146
|
+
|
|
147
|
+
## Optional failure review
|
|
148
|
+
|
|
149
|
+
Create a normal recurring schedule with `--every-seconds 900 --when unreviewed-failures --text-file templates/failure-review.md`. The condition advances empty occurrences without launching an executor. It considers only failures belonging to the paired owner. No separate monitor or automatic retry is introduced.
|
|
150
|
+
|
|
151
|
+
`failures [--all] [--limit N]` returns failedAt, reason, exit code, native session, captured error and runtime versions. Capture keeps at most 4 KiB of redacted stderr; historical failures are not backfilled. `run RUN_ID` reads an owned run. `review RUN_ID --failed-at ISO --status resolved|attention --diagnosis TEXT --recovery TEXT --outcome TEXT` records the investigation without rewriting execution history. A stale timestamp is rejected; a later failure needs a new review. Restricted reply, external and isolated-task callers cannot review failures. An attention review is handed off, not repeatedly relaunched; another new failure wakes the next review.
|
|
152
|
+
|
|
153
|
+
The prompt controls diagnosis, authorized recovery and quiet notification behavior. Inspect prior effects and receipts before retrying anything. A failed review run itself remains visible as a new failure for the next occurrence.
|
|
@@ -33,7 +33,7 @@ Infer the complete job from ordinary language:
|
|
|
33
33
|
|
|
34
34
|
Apply the required core confirmation to the concrete proposal, not an extra
|
|
35
35
|
questionnaire. Use the owner's existing contact, purpose and disclosure limits.
|
|
36
|
-
|
|
36
|
+
For an ongoing incoming-only conversation, use `--until-revoked`; finite tasks remain bounded. Explain the expiry only
|
|
37
37
|
when it matters to that proposed job; never silently expand or renew permission.
|
|
38
38
|
|
|
39
39
|
## Three capture modes, separate reply authority
|
|
@@ -83,9 +83,17 @@ registered source, canonical contact, purpose, explicitly shareable context file
|
|
|
83
83
|
and expiry. No need to invent a booking objective: “conversational replies to
|
|
84
84
|
this contact, no private disclosures or commitments” is a legitimate purpose.
|
|
85
85
|
If no private facts may be shared, say so in the context file; do not include
|
|
86
|
-
owner memory.
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
owner memory. Finite jobs support at most 72 hours. For an ongoing conversation requested by
|
|
87
|
+
the owner, add `--incoming-only --until-revoked`. This authorizes that exact
|
|
88
|
+
conversation until owner revocation, without a total reply quota. Do not silently
|
|
89
|
+
convert a finite job into an ongoing permission.
|
|
90
|
+
|
|
91
|
+
For Telegram groups, the relay registers source `telegram` automatically when
|
|
92
|
+
paired. Use the exact negative group ID from discovery, never its display name.
|
|
93
|
+
Keep the shared context limited to what every group member may know. All human
|
|
94
|
+
members of that approved group may converse; this does not grant owner tools.
|
|
95
|
+
The group runs with the existing restricted messaging runner, and its notes are
|
|
96
|
+
separate from private PA memory. Group text is supported; media is not yet.
|
|
89
97
|
|
|
90
98
|
The core presents the exact proposal for owner confirmation. Ordinary messages
|
|
91
99
|
inside that grant need no repeated confirmations. Incoming-only grants create
|
package/docs/setup.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Agent-led setup
|
|
2
2
|
|
|
3
|
+
## A group as owner
|
|
4
|
+
|
|
5
|
+
An installer can explicitly approve one Telegram group as the agent's owner.
|
|
6
|
+
Every human member in that group then has owner access, including settings,
|
|
7
|
+
approvals and scheduling. The conversation and replies are shared in that group.
|
|
8
|
+
New members inherit this access. Bots and anonymous sender-chat posts are ignored.
|
|
9
|
+
|
|
10
|
+
Add the bot to the intended group and send it a message addressed to its username.
|
|
11
|
+
`ezenciel-agents-owner status` shows the pending group title and negative chat ID;
|
|
12
|
+
verify the exact group with the installer, then run
|
|
13
|
+
`ezenciel-agents-owner approve-group <negative-chat-id>`. Pairing never happens
|
|
14
|
+
automatically. The existing `approve <user-id>` command approves DMs only.
|
|
15
|
+
An existing owner must be explicitly revoked before replacement; use a fresh
|
|
16
|
+
deployment when its existing workspace contains information unsuitable for the group.
|
|
17
|
+
|
|
18
|
+
Make the bot a group admin so Telegram delivers ordinary member messages and
|
|
19
|
+
allows membership verification for approval and control buttons. Group ownership only authorizes
|
|
20
|
+
the exact approved chat, not other groups or members' private DMs. Telegram group
|
|
21
|
+
migration to a new chat ID requires a new explicit binding. Verify a real group
|
|
22
|
+
reply before considering setup complete. All other groups retain their existing
|
|
23
|
+
restricted conversation-grant behavior.
|
|
24
|
+
|
|
3
25
|
Ez supports two independent, composable uses: an autonomous Telegram agent and
|
|
4
26
|
workspace-scoped plugins called by your existing local CLI/GUI executor.
|
|
5
27
|
For CLI-only requests, follow [standalone setup](standalone-cli.md): no Telegram
|
|
@@ -30,6 +52,23 @@ owner request for standalone plugin development is a separate workflow.
|
|
|
30
52
|
|
|
31
53
|
## Defaults and host prerequisites
|
|
32
54
|
|
|
55
|
+
New agents using `codex` or `codex-gui` start with `gpt-5.6-terra` and `high`
|
|
56
|
+
reasoning, including when initialized with `ezenciel-agents-setup init`. This
|
|
57
|
+
Ez default takes precedence over discovered host client defaults. Saved agent
|
|
58
|
+
selections at or below high are preserved; use the AI settings to choose another model or effort.
|
|
59
|
+
Ez rejects explicit reasoning above `high` for every model at selection and execution,
|
|
60
|
+
including old saved or queued choices. Unset Codex model/effort resolves to
|
|
61
|
+
Terra/high at launch. This governs Ez-managed launches; it is not an account-wide
|
|
62
|
+
limit on independently launched native clients or executor-created native subagents.
|
|
63
|
+
Other adapters inherit their native effort when none is selected in Ez. That
|
|
64
|
+
inherited configuration is not capped by Ez; explicit above-high Ez selections
|
|
65
|
+
are still rejected. Only Codex adapters receive the default `high` effort.
|
|
66
|
+
|
|
67
|
+
New scheduled and one-off background tasks default to Codex Terra/high independently
|
|
68
|
+
of the creating chat. Use scheduler `--cli`, `--model`, and `--effort` flags for
|
|
69
|
+
explicit overrides. Editing a schedule preserves its settings unless overridden.
|
|
70
|
+
Restricted messaging tasks also use Terra/high when no settings are supplied.
|
|
71
|
+
|
|
33
72
|
Use the existing owner's host account. Unless a layout was supplied, use
|
|
34
73
|
`${XDG_DATA_HOME:-$HOME/.local/share}/ez/packages/<version>/` for extracted main
|
|
35
74
|
packages and `${XDG_DATA_HOME:-$HOME/.local/share}/ez/agents/` for private deployments.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Verified beta publication
|
|
2
|
+
|
|
3
|
+
The Mac prepares and independently tests the release. GitHub-hosted Actions
|
|
4
|
+
publishes the exact approved tarball using npm OIDC. The shared implementation
|
|
5
|
+
is `.github/workflows/npm-beta-shared.yml` in this repository; each package has a
|
|
6
|
+
small manually dispatched `publish-beta.yml` caller. This setup does not grant
|
|
7
|
+
release authority or establish npm trust automatically.
|
|
8
|
+
|
|
9
|
+
## Enroll a repository once
|
|
10
|
+
|
|
11
|
+
1. Verify the source is public and explicitly registered in the current
|
|
12
|
+
[public catalog](plugin-catalog.md), or is core itself. Registration does not
|
|
13
|
+
prove a registry release exists. Excluded or private repositories cannot use
|
|
14
|
+
this publisher.
|
|
15
|
+
2. Review and merge the shared publisher first. Generate the caller using its
|
|
16
|
+
full immutable commit SHA and the repository's required CI check names:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
node scripts/generate-publish-caller.mjs jdorado/ez-whatsapp \
|
|
20
|
+
@jc_stack/ez-whatsapp FULL_SHARED_COMMIT_SHA \
|
|
21
|
+
'["test (ubuntu-latest, 22)","test (ubuntu-latest, 24)","test (macos-latest, 22)","test (macos-latest, 24)","docker"]' > publish-beta.yml
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Put that file in the plugin's `.github/workflows/` through its own reviewed
|
|
25
|
+
PR. Inspect the actual CI names; the example does not establish the policy.
|
|
26
|
+
Keep the reusable workflow reference and `publisher-sha` on the same reviewed
|
|
27
|
+
commit. Core uses a local reusable workflow at the dispatch commit. The
|
|
28
|
+
generator writes stdout only; it never grants repository scope or edits npm.
|
|
29
|
+
3. The npm package owner authenticates separately and enrolls the exact caller
|
|
30
|
+
repository and filename `publish-beta.yml` as a trusted publisher. Enable
|
|
31
|
+
**direct publication** explicitly; new trust configurations can default to
|
|
32
|
+
staged publication only. If an environment is configured on npm, add that
|
|
33
|
+
exact environment to the shared publishing job through review before use.
|
|
34
|
+
This workflow currently uses no environment.
|
|
35
|
+
4. Verify enrollment through npm settings or `npm trust list PACKAGE`. npm
|
|
36
|
+
validates the **calling** workflow for reusable workflows. Both caller and
|
|
37
|
+
publishing job need `id-token: write`; test/validation jobs do not receive it. GitHub requires `contents: write`
|
|
38
|
+
to read unpublished draft assets: only the validation job receives that
|
|
39
|
+
capability and makes GET requests only. The separate OIDC job has
|
|
40
|
+
`contents: read`. No package code or lifecycle scripts run in validation.
|
|
41
|
+
Do not add `NODE_AUTH_TOKEN`, npm tokens, or private profiles to these jobs.
|
|
42
|
+
|
|
43
|
+
An npm package must already exist before trust enrollment. If a registered
|
|
44
|
+
plugin has no registry package, the owner must perform a real, approved initial
|
|
45
|
+
beta publication using authenticated npm, with all release checks and exact
|
|
46
|
+
artifact readback. Then enroll trust. Do not create a dummy release to test
|
|
47
|
+
login. A missing GitHub release likewise remains missing until actually created
|
|
48
|
+
and read back; a catalog link or workflow PR is not publication evidence.
|
|
49
|
+
|
|
50
|
+
Current npm requirements and enrollment fields are documented in
|
|
51
|
+
[npm trusted publishers](https://docs.npmjs.com/trusted-publishers/) and
|
|
52
|
+
[npm trust](https://docs.npmjs.com/cli/v11/commands/npm-trust/).
|
|
53
|
+
The workflow uses Node 24 and npm 11.19.1 on GitHub-hosted Ubuntu runners.
|
|
54
|
+
|
|
55
|
+
## Prepare and dispatch one beta
|
|
56
|
+
|
|
57
|
+
Follow [releasing](releasing.md), including isolated Mac tests, packed artifact
|
|
58
|
+
inspection, independent review, required CI and authorized merge. The source
|
|
59
|
+
must be the exact current `main` commit; after a merge, verify that its tree
|
|
60
|
+
matches the tested source and renew invalidated evidence. Wait for that commit's
|
|
61
|
+
CI from `.github/workflows/ci.yml`, triggered by a push on `main`. The caller's reviewed list of required checks is a fail-closed minimum;
|
|
62
|
+
update it when repository policy adds checks. The validator selects the newest
|
|
63
|
+
main-push CI run for that source before checking success, then requires the named
|
|
64
|
+
jobs from its latest attempt. Tag, PR and other workflow runs cannot shadow it;
|
|
65
|
+
failed, pending or incomplete main CI cannot fall back to an older success.
|
|
66
|
+
|
|
67
|
+
Create the `vVERSION` tag at that exact source commit and a **draft prerelease**
|
|
68
|
+
with these assets, using native `gh release create --draft --prerelease` and
|
|
69
|
+
`gh release upload` under existing release authority:
|
|
70
|
+
|
|
71
|
+
- `candidate.tgz`: the exact Mac-tested bytes from `npm pack --ignore-scripts`.
|
|
72
|
+
Do not rebuild it on Actions.
|
|
73
|
+
- `release-receipt.json`: a sanitized record with this shape:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"repository": "jdorado/ez-agents",
|
|
78
|
+
"package": "@jc_stack/ez-agents",
|
|
79
|
+
"version": "0.1.0-beta.14",
|
|
80
|
+
"sourceSha": "FULL_TESTED_MAIN_COMMIT_SHA",
|
|
81
|
+
"sha256": "SHA256_OF_CANDIDATE_TGZ",
|
|
82
|
+
"independentReviewUrl": "https://github.com/jdorado/ez-agents/pull/PR_NUMBER#issuecomment-ID",
|
|
83
|
+
"testEvidenceUrls": ["https://github.com/jdorado/ez-agents/actions/runs/RUN_ID"]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The maintainer verifies those evidence links substantiate independent final-diff
|
|
88
|
+
review, artifact tests and accepted beta limitations before dispatch. The receipt
|
|
89
|
+
binds that attestation to the commit and digest; a syntactically valid URL alone
|
|
90
|
+
cannot prove review quality or release authority.
|
|
91
|
+
|
|
92
|
+
Read the draft's numeric `id` with `gh api repos/OWNER/REPO/releases` (the
|
|
93
|
+
release-by-tag API only returns published releases). Dispatch `publish-beta.yml`
|
|
94
|
+
on `main` with `release-id`, `version`, `source-sha` and
|
|
95
|
+
`artifact-sha256`. Copy the digest from the independently verified Mac receipt,
|
|
96
|
+
not an unreviewed replacement release asset. The validator checks current public
|
|
97
|
+
scope, source/tag identity, required GitHub Actions checks, receipt identity,
|
|
98
|
+
package metadata and tarball hash. It transfers the validated bytes using an
|
|
99
|
+
immutable Actions artifact ID. A fresh job revalidates before publication and
|
|
100
|
+
runs npm from a clean directory without package lifecycle scripts.
|
|
101
|
+
|
|
102
|
+
Only `X.Y.Z-beta.N` versions are supported. Publish to npm `latest` so the
|
|
103
|
+
package page and default installs show the newest approved release. Manifests
|
|
104
|
+
must use `publishConfig.tag: "latest"` (or omit the tag). The version remains a
|
|
105
|
+
SemVer prerelease and the GitHub release remains a prerelease. Private
|
|
106
|
+
packages, wrong package/repository identities and stable versions fail before
|
|
107
|
+
publication. No npm login smoke publication or stable-version release occurs.
|
|
108
|
+
|
|
109
|
+
## Readback, failure and release completion
|
|
110
|
+
|
|
111
|
+
The publisher reads registry metadata, checks that `latest` identifies the released version and downloads the
|
|
112
|
+
published tarball to compare its SHA-256. Preserve the workflow's readback receipt, run URL and source/artifact
|
|
113
|
+
identity on the release PR. A failed command after the publish call may mean npm
|
|
114
|
+
accepted it: inspect registry state first. A rerun may verify an existing exact
|
|
115
|
+
version; if the version is absent it refuses a second write. Reconcile first,
|
|
116
|
+
then create a fresh authorized dispatch if appropriate. Never repeat or overwrite that version or silently repair tags.
|
|
117
|
+
Missing trust or registry access is an external dependency, not a reason to use
|
|
118
|
+
a token workaround.
|
|
119
|
+
|
|
120
|
+
After successful registry readback, finish the GitHub prerelease with the tested
|
|
121
|
+
artifact/checksum and verify its public availability. Perform the clean-host
|
|
122
|
+
installation and runtime/provider checks required by the package's release
|
|
123
|
+
rules. Actions success proves registry delivery only; it does not prove a
|
|
124
|
+
running agent was upgraded. Respect each installation's saved update policy.
|
|
125
|
+
|
|
126
|
+
## Migration from the legacy beta tag
|
|
127
|
+
|
|
128
|
+
Existing plugin callers are SHA-pinned: regenerate each caller against the merged
|
|
129
|
+
shared-publisher revision and update its package publishConfig together through
|
|
130
|
+
review. Old pins retain the old behavior. Do not mutate an already staged or
|
|
131
|
+
published artifact; prepare a new version when package metadata changes.
|
|
132
|
+
|
|
133
|
+
The publisher uses one native npm publish operation with OIDC and `--tag latest`.
|
|
134
|
+
It does not synchronize the legacy `beta` tag: npm trusted publishing does not
|
|
135
|
+
support standalone dist-tag changes. No extra registry token is needed. Ez beta
|
|
136
|
+
update discovery considers both latest and legacy beta during migration; stable-only
|
|
137
|
+
policies select non-deprecated stable versions and cannot automatically install a
|
|
138
|
+
prerelease. Older installed updaters still following only beta require an explicit
|
|
139
|
+
exact-version update to a core release containing this discovery change. Existing
|
|
140
|
+
registry versions/tags are not changed by merging the publisher.
|
package/docs/upgrades.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Available in this beta. Earlier main upgrade/rollback VM QA passed; final-release
|
|
4
4
|
fresh-host/reboot and live plugin upgrade acceptance remain pending. npm
|
|
5
|
-
publication is not required to test this feature.
|
|
6
|
-
automatic channel
|
|
5
|
+
publication is not required to test this feature. The beta channel is the default
|
|
6
|
+
automatic channel for core and plugins without a saved policy. Existing explicit
|
|
7
|
+
stable or manual policies are preserved. The owner may select either per target.
|
|
7
8
|
The main target and installed plugins version independently.
|
|
8
9
|
|
|
9
10
|
The agent owns release review, policy decisions and communication. The host
|
|
@@ -109,8 +110,8 @@ live. A healthy container alone does not prove a Telegram or plugin reply.
|
|
|
109
110
|
|
|
110
111
|
```sh
|
|
111
112
|
ez updates check
|
|
112
|
-
ez updates policy main # defaults: automatic,
|
|
113
|
-
ez updates policy whatsapp
|
|
113
|
+
ez updates policy main # defaults: automatic, beta
|
|
114
|
+
ez updates policy whatsapp stable # opt into stable-only updates
|
|
114
115
|
ez updates policy main manual # disable unattended upgrades
|
|
115
116
|
ez updates prepare main --version 0.1.0-beta.4
|
|
116
117
|
# Or a local candidate, independently of npm:
|
|
@@ -191,3 +192,22 @@ changes also need supervisor restart and requesting-process-exit tests. Run
|
|
|
191
192
|
`node docker/upgrade-smoke.mjs` with a local `EZ_WHATSAPP_SOURCE` containing the
|
|
192
193
|
WhatsApp fixture. The smoke uses synthetic transport only. VM installation,
|
|
193
194
|
agent-led upgrades, restart and real account acceptance remain separate QA gates.
|
|
195
|
+
|
|
196
|
+
Beta policy discovers the newer of npm latest and the legacy beta tag. Stable-only
|
|
197
|
+
policy selects non-deprecated stable versions, even when latest is a prerelease.
|
|
198
|
+
No eligible version is reported as available:null with newer:false. Older installed
|
|
199
|
+
updaters need an exact-version core update to adopt this discovery behavior.
|
|
200
|
+
|
|
201
|
+
## Shared agent guidance
|
|
202
|
+
|
|
203
|
+
Ez includes `templates/agent-guidance.md` from the running package in every
|
|
204
|
+
owner-worker prompt, including resumed CLI and desktop conversations and scheduled
|
|
205
|
+
owner work. After the runtime upgrades, the next turn receives the new guidance.
|
|
206
|
+
An already running turn keeps its original prompt. Restricted contact tasks and
|
|
207
|
+
reply-only workers retain their separate, bounded instructions.
|
|
208
|
+
|
|
209
|
+
Keep general operating defaults in this package-owned file. Keep agent purpose,
|
|
210
|
+
preferences and local conventions in the workspace's `AGENTS.md`, `SOUL.md`,
|
|
211
|
+
`USER.md` and memory files; upgrades preserve them. Shared guidance does not
|
|
212
|
+
grant permissions, and explicit owner instructions take precedence over its
|
|
213
|
+
defaults within existing execution permissions.
|
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.18",
|
|
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.",
|
|
@@ -47,11 +47,14 @@
|
|
|
47
47
|
"test",
|
|
48
48
|
"tsconfig.json",
|
|
49
49
|
"scripts/assert-local-registry.mjs",
|
|
50
|
-
".dockerignore"
|
|
50
|
+
".dockerignore",
|
|
51
|
+
"scripts/trusted-beta.mjs",
|
|
52
|
+
"scripts/generate-publish-caller.mjs",
|
|
53
|
+
"scripts/smoke-busy-reply.ts"
|
|
51
54
|
],
|
|
52
55
|
"publishConfig": {
|
|
53
56
|
"access": "public",
|
|
54
|
-
"tag": "
|
|
57
|
+
"tag": "latest"
|
|
55
58
|
},
|
|
56
59
|
"scripts": {
|
|
57
60
|
"setup": "tsx --env-file-if-exists=.env src/setup.ts",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Print a reviewed caller. Does not write repositories or enroll npm trust.
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
export function generateCaller({ repository, packageName, publisherSha, checks }) {
|
|
7
|
+
assert.match(repository, /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/, 'Invalid repository');
|
|
8
|
+
assert.match(packageName, /^@[a-z0-9_-]+\/[a-z0-9_.-]+$/, 'Expected scoped npm package');
|
|
9
|
+
assert.match(publisherSha, /^[a-f0-9]{40}$/, 'Pin the reviewed shared publisher to a full commit SHA');
|
|
10
|
+
assert(Array.isArray(checks) && checks.length > 0 && checks.every(c => typeof c === 'string' && /^[A-Za-z0-9 (),_.-]+$/.test(c)), 'Required CI check names are mandatory');
|
|
11
|
+
const core = repository === 'jdorado/ez-agents';
|
|
12
|
+
const reference = core ? './.github/workflows/npm-beta-shared.yml' : `jdorado/ez-agents/.github/workflows/npm-beta-shared.yml@${publisherSha}`;
|
|
13
|
+
return `# Generated by scripts/generate-publish-caller.mjs; review changes before merging.
|
|
14
|
+
name: Publish verified npm beta
|
|
15
|
+
on:
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
inputs:
|
|
18
|
+
release-id:
|
|
19
|
+
description: 'Numeric ID of the staged draft prerelease'
|
|
20
|
+
required: true
|
|
21
|
+
type: string
|
|
22
|
+
version:
|
|
23
|
+
description: 'Reviewed beta version staged as draft release vVERSION'
|
|
24
|
+
required: true
|
|
25
|
+
type: string
|
|
26
|
+
source-sha:
|
|
27
|
+
description: 'Full tested commit SHA; must be current main'
|
|
28
|
+
required: true
|
|
29
|
+
type: string
|
|
30
|
+
artifact-sha256:
|
|
31
|
+
description: 'SHA-256 of the exact independently verified candidate.tgz'
|
|
32
|
+
required: true
|
|
33
|
+
type: string
|
|
34
|
+
permissions: {}
|
|
35
|
+
jobs:
|
|
36
|
+
publish:
|
|
37
|
+
if: github.repository == '${repository}' && github.ref == 'refs/heads/main'
|
|
38
|
+
permissions:
|
|
39
|
+
contents: write
|
|
40
|
+
checks: read
|
|
41
|
+
actions: read
|
|
42
|
+
pull-requests: read
|
|
43
|
+
id-token: write
|
|
44
|
+
uses: ${reference}
|
|
45
|
+
with:
|
|
46
|
+
package: '${packageName}'
|
|
47
|
+
publisher-sha: ${core ? '${{ github.sha }}' : `'${publisherSha}'`}
|
|
48
|
+
required-checks: '${JSON.stringify(checks)}'
|
|
49
|
+
release-id: \${{ inputs.release-id }}
|
|
50
|
+
version: \${{ inputs.version }}
|
|
51
|
+
source-sha: \${{ inputs.source-sha }}
|
|
52
|
+
artifact-sha256: \${{ inputs.artifact-sha256 }}
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
57
|
+
const [repository, packageName, publisherSha, checksJson, ...extra] = process.argv.slice(2);
|
|
58
|
+
if (!checksJson || extra.length) throw new Error('Usage: node scripts/generate-publish-caller.mjs OWNER/REPO @SCOPE/PACKAGE SHARED_COMMIT_SHA \'["required CI name"]\'');
|
|
59
|
+
process.stdout.write(generateCaller({ repository, packageName, publisherSha, checks: JSON.parse(checksJson) }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Real restricted Codex reply while a synthetic writer stays active. No Telegram network.
|
|
2
|
+
import { mkdtemp, mkdir, writeFile, symlink } from 'node:fs/promises'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { spawn } from 'node:child_process'
|
|
6
|
+
import { createRelay } from '../src/index.js'
|
|
7
|
+
import { RunStore } from '../src/runs.js'
|
|
8
|
+
import { ControlStore } from '../src/control-state.js'
|
|
9
|
+
import { initialPreset } from '../src/ai.js'
|
|
10
|
+
import { startExecutorJob } from '../src/executor.js'
|
|
11
|
+
import { serveHostExecutor } from '../src/host-executor.js'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
import { initializeWorkspace } from '../src/workspace.js'
|
|
14
|
+
import type { Update } from 'grammy/types'
|
|
15
|
+
if (process.argv.includes('--host')) {
|
|
16
|
+
const root=process.argv[process.argv.indexOf('--host')+1], abort=new AbortController()
|
|
17
|
+
process.once('SIGTERM',()=>abort.abort())
|
|
18
|
+
await serveHostExecutor({cli:'codex',agents:[{name:'fixture',workspace:join(root,'mind'),controlDir:join(root,'control'),binDir:fileURLToPath(new URL('../bin',import.meta.url)),sharedWorkspace:join(root,'mind')}]},abort.signal,async(texts,options)=>{
|
|
19
|
+
if((await new RunStore(options.controlDir).get(options.runId))?.replyOnly)return startExecutorJob(texts,options)
|
|
20
|
+
const child=spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{detached:true,stdio:['pipe','pipe','pipe']})
|
|
21
|
+
return {child,stdout:'',cleanup:async()=>{}}
|
|
22
|
+
})
|
|
23
|
+
process.exit(0)
|
|
24
|
+
}
|
|
25
|
+
const root=await mkdtemp(join(tmpdir(),'ez-busy-reply-')), workspace=join(root,'mind'), controlDir=join(root,'control')
|
|
26
|
+
await initializeWorkspace(workspace);await mkdir(controlDir,{recursive:true})
|
|
27
|
+
if(process.env.EZ_REPLY_QA_AUTH){await mkdir(join(controlDir,'cli','codex'),{recursive:true});await symlink(process.env.EZ_REPLY_QA_AUTH,join(controlDir,'cli','codex','auth.json'))}
|
|
28
|
+
const hostMode=process.argv.includes('--transport')
|
|
29
|
+
const hostEnvironment={...process.env};delete hostEnvironment.EZ_EXECUTOR_TRANSPORT
|
|
30
|
+
const host=hostMode?spawn(process.execPath,['--import',fileURLToPath(new URL('../node_modules/tsx/dist/loader.mjs',import.meta.url)),fileURLToPath(import.meta.url),'--host',root],{env:hostEnvironment,stdio:['ignore','inherit','inherit']}):undefined
|
|
31
|
+
if(hostMode)process.env.EZ_EXECUTOR_TRANSPORT='host'
|
|
32
|
+
const control=new ControlStore(controlDir,1000),runs=new RunStore(controlDir)
|
|
33
|
+
await control.requestPairing(101,101);await control.approveOwner(101)
|
|
34
|
+
let writer:any,replyEvents=''
|
|
35
|
+
const relay=createRelay({workspace,controlDir,pairingTtlMs:1000,executorTimeoutMs:0,executorCli:'codex',telegramBotToken:'fixture'},async(texts,options)=>{
|
|
36
|
+
if(hostMode)return startExecutorJob(texts,options)
|
|
37
|
+
const run=await runs.get(options.runId)
|
|
38
|
+
if(run?.replyOnly){const job=await startExecutorJob(texts,options);job.child.stdout?.on('data',c=>{replyEvents+=c});return job}
|
|
39
|
+
const child=spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:['pipe','pipe','pipe']});writer=child
|
|
40
|
+
return {child,stdout:'',cleanup:async()=>{}}
|
|
41
|
+
})
|
|
42
|
+
relay.bot.botInfo={id:999,is_bot:true,first_name:'Fixture',username:'fixture_bot'} as any
|
|
43
|
+
const replies:string[]=[]
|
|
44
|
+
relay.bot.api.config.use(async(_p,method,payload)=>{if(method==='sendMessage')replies.push((payload as any).text);return {ok:true,result:method==='sendMessage'?{message_id:replies.length,date:0,chat:{id:101,type:'private'},text:(payload as any).text}:true} as any})
|
|
45
|
+
const msg=(id:number,text:string):Update=>({update_id:id,message:{message_id:id,date:0,text,from:{id:101,is_bot:false,first_name:'Fixture'},chat:{id:101,type:'private',first_name:'Fixture'}}})
|
|
46
|
+
try{
|
|
47
|
+
await relay.bot.handleUpdate(msg(1,'Long work'));await relay.drainInbox(true)
|
|
48
|
+
await relay.bot.handleUpdate(msg(2,'What is running? Also calculate 17 times 19. Use your available reply tools. Do not queue any work.'));await relay.drainInbox(true)
|
|
49
|
+
const started=Date.now()
|
|
50
|
+
while(!replies.length && Date.now()-started<120000){await relay.drainOutbox();await new Promise(r=>setTimeout(r,250))}
|
|
51
|
+
if(!replies.some(s=>s.includes('323')))throw new Error('No verified arithmetic reply: '+JSON.stringify(replies))
|
|
52
|
+
if((await runs.get('tg_1'))?.status!=='running')throw new Error('Writer stopped')
|
|
53
|
+
while((await runs.get('tg_2'))?.status==='running' && Date.now()-started<120000)await new Promise(r=>setTimeout(r,250))
|
|
54
|
+
if((await runs.get('tg_2'))?.status!=='completed')throw new Error('Reply did not finish successfully')
|
|
55
|
+
const reply=await runs.get('tg_2');if(!reply?.replyOnly)throw new Error('No restricted reply lane')
|
|
56
|
+
await writeFile(join(root,'evidence.json'),JSON.stringify({replyMs:Date.now()-started,replies,writerRunning:(await runs.get('tg_1'))?.status==='running',replyEvents},null,2))
|
|
57
|
+
console.log(JSON.stringify({root,replyMs:Date.now()-started,replies,writerRunning:true}))
|
|
58
|
+
}finally{await relay.stop();writer?.kill();host?.kill();delete process.env.EZ_EXECUTOR_TRANSPORT}
|