@ethlete/agent-rules 0.1.0-next.28 → 0.1.0-next.29

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @ethlete/agent-rules
2
2
 
3
+ ## 0.1.0-next.29
4
+
5
+ ### Patch Changes
6
+
7
+ - `agent-sessions restart` now waits until the session ended its turn and its prompt is empty, so it no longer types into a running turn or into your draft.
8
+ - A multi-target sketch merge gives each target only the `shared/` files it imports, directly or transitively; an unimported file returns to the target that held it. `sketch new` refuses targets whose `shared/` files clash.
9
+ - `et design sketch new` takes several parts: one sketch, one variant folder holding them all, one `ready`, one merge; `ready` and `preview` name every part a `shared/` edit reaches; Studio shows every target part.
10
+
3
11
  ## 0.1.0-next.28
4
12
 
5
13
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "e2e-progress",
3
- "version": "0.1.0-next.28",
3
+ "version": "0.1.0-next.29",
4
4
  "description": "Live Playwright e2e progress from a run log, as a status line entry and a pane",
5
5
  "types": "./types/index.d.ts"
6
6
  }
@@ -49,7 +49,7 @@ Nothing runs until you start it, so a person who does not use this does nothing.
49
49
 
50
50
  `up codex=codex gemini=gemini` starts a detached `tmux` session per agent, named like its sender name, and runs the command in it. It also starts a session named `mailbox-dispatch` that runs `dispatch`. A running session is left alone. Attach to a session with `tmux attach -t <name>`. Detach with `Ctrl-b`, then `d`. The session and its agent keep running. Use `down` to stop the dispatcher and the named sessions.
51
51
 
52
- `restart <name> <prompt...>` types the clear command (`/clear`, or `/new` for a `codex` name) and then the prompt into the session `<name>@<repo>` after `--after` seconds (default 5). The command returns at once, so end your turn after it. An agent uses it to clear itself and resume from its saved work state. It refuses any session other than the caller's own (from `$TMUX_PANE`), and the error names the caller's own sender name. Background commands of the old session still report into the new one.
52
+ `restart <name> <prompt...>` types the clear command (`/clear`, or `/new` for a `codex` name) and then the prompt into the session `<name>@<repo>`, no sooner than `--after` seconds (default 5). It waits until that session ended its turn and its prompt holds no typed text, and gives up after 30 minutes. The command returns at once, so end your turn after it. An agent uses it to clear itself and resume from its saved work state. It refuses any session other than the caller's own (from `$TMUX_PANE`), and the error names the caller's own sender name. Background commands of the old session still report into the new one.
53
53
 
54
54
  `shell-install` writes one shell function per agent into `~/.zshrc` or `~/.bashrc`, between two marker lines. A second run replaces the block, and `--remove` deletes it. Use `--file` for another startup file and `--dry-run` to preview. After a new terminal opens, typing `claude` or `codex` in a repo with this skill runs the agent through `run`. Elsewhere the function starts the plain command. Run `shell-install` only when the user asks: it changes a file the user owns. `shell-init` prints the same functions without writing a file.
55
55
 
@@ -35,6 +35,8 @@ const WAKE_GRACE_MS = 1500;
35
35
  const DRAFT_RETRY_MS = 2000;
36
36
  const RESTART_AFTER_SECONDS = 5;
37
37
  const RESTART_CLEAR_MS = 2500;
38
+ const RESTART_POLL_MS = 2000;
39
+ const RESTART_WAIT_MS = 30 * 60 * 1000;
38
40
  const repoSlug = basename(root).replace(/[.:]/g, '-');
39
41
  const sessionFor = (agent) => `${agent}@${repoSlug}`;
40
42
  const DISPATCH_SESSION = process.env.AGENT_MAILBOX_DISPATCH_SESSION ?? `mailbox-dispatch@${repoSlug}`;
@@ -102,6 +104,20 @@ const runsInSession = (agentArgs) =>
102
104
  * wake-up line from answering a permission dialog.
103
105
  */
104
106
  const SGR = `${String.fromCharCode(27)}\\[([0-9;]*)m`;
107
+ const isBusy = (session) => /esc to inte/i.test(tmux('capture-pane', '-p', '-t', `=${session}:`).stdout ?? '');
108
+
109
+ /** Waits until the agent ended its turn and nobody types in its prompt, twice in a row; `false` after the time limit. */
110
+ const waitForQuiet = (session) => {
111
+ const end = Date.now() + RESTART_WAIT_MS;
112
+ let quiet = 0;
113
+ while (Date.now() < end) {
114
+ quiet = isBusy(session) || hasDraft(session) ? 0 : quiet + 1;
115
+ if (quiet === 2) return true;
116
+ sleep(RESTART_POLL_MS);
117
+ }
118
+ return false;
119
+ };
120
+
105
121
  const hasDraft = (session) => {
106
122
  const pane = tmux('capture-pane', '-p', '-e', '-t', `=${session}:`).stdout ?? '';
107
123
  const prompt = pane
@@ -311,20 +327,28 @@ if (command === 'send' && sender) {
311
327
  } else if (now) {
312
328
  const type = (...keys) => tmux('send-keys', '-t', `=${session}:`, ...keys);
313
329
  sleep(Number(afterSeconds ?? RESTART_AFTER_SECONDS) * 1000);
314
- type('-l', sender.startsWith('codex') ? '/new' : '/clear');
315
- sleep(ENTER_DELAY_MS);
316
- type('Enter');
317
- sleep(RESTART_CLEAR_MS);
318
- type('-l', words.join(' '));
319
- sleep(ENTER_DELAY_MS);
320
- type('Enter');
330
+ if (!waitForQuiet(session)) {
331
+ process.exitCode = 1;
332
+ } else {
333
+ type('-l', sender.startsWith('codex') ? '/new' : '/clear');
334
+ sleep(ENTER_DELAY_MS);
335
+ type('Enter');
336
+ sleep(RESTART_CLEAR_MS);
337
+ if (waitForQuiet(session)) {
338
+ type('-l', words.join(' '));
339
+ sleep(ENTER_DELAY_MS);
340
+ type('Enter');
341
+ }
342
+ }
321
343
  } else {
322
344
  const delay = Number(afterSeconds ?? RESTART_AFTER_SECONDS);
323
345
  spawn(process.execPath, [script, 'restart', sender, '--now', '--after', String(delay), ...words], {
324
346
  detached: true,
325
347
  stdio: 'ignore',
326
348
  }).unref();
327
- process.stdout.write(`Restarting ${session} in ${delay} seconds. End your turn now.\n`);
349
+ process.stdout.write(
350
+ `Restarting ${session} once its turn ended and its prompt is empty, ${delay} seconds at the earliest. End your turn now.\n`,
351
+ );
328
352
  }
329
353
  } else if (command === 'down') {
330
354
  [DISPATCH_SESSION, ...[sender, ...words].filter(Boolean).map(sessionFor)].forEach((name) => {
@@ -115,6 +115,7 @@ answers. Variants are drawn at the same geometry, and one variant wins only agai
115
115
 
116
116
  ```bash
117
117
  et design sketch new <layer>/<name> --variants a,b --by <model id> [--like <layer>/<name>] [--variant-note a=<text>]… [--product <p>] # each variant starts as a copy of the part
118
+ et design sketch new <layer>/<name> <layer>/<name>… --variants a --by <model id> # one change across several parts: one sketch, one ready, one merge
118
119
  et design sketch report <layer>/<name> --variant b --by <model id> "phone" # tell Studio what you build now
119
120
  et design sketch report <layer>/<name> --variant b --by <model id> --done # you stopped drawing b; Studio shows its frames
120
121
  et design sketch check [<layer>/<name>] [--fix] # each variant laid over the product
@@ -123,6 +124,10 @@ et design sketch reject <layer>/<name> --variant b [--reason <text>] --by <model
123
124
  et design sketch reopen <layer>/<name> --variant b [--reason <text>] --by <model id> # only when the user takes b back; the rejection stays in the history
124
125
  ```
125
126
 
127
+ One change across several parts (an alias removal, a token rename) is one sketch with several targets: each
128
+ variant holds every target, and `ready` compares them all. `ready` and `preview` also name every part a `shared/`
129
+ edit reaches.
130
+
126
131
  Variants are alternatives. A variant never stands for a state of another variant: a hidden or
127
132
  collapsed mode of variant d is a further state of d, not a variant e. Before you draw a new control,
128
133
  look for a control that already does the job (an active rail button that toggles its panel), and
@@ -162,7 +167,9 @@ variant imports it as `@shared/<path>`, and a shared file may import `@system/..
162
167
  changed holds its own copy at `<variant>/shared/<path>`; `@shared/<path>` finds that copy first, and a merge
163
168
  of that variant takes it. On a merge, `shared/` moves into
164
169
  the target's folder as `shared/` with its imports made relative; when no part imports it, it is
165
- dropped. Only the target may import it: a merge refuses while another part of the picked variant
170
+ dropped. With several targets, each takes only the files it imports, directly or through another
171
+ shared file; a file nobody imports goes back to the target that held it, and the merge refuses one no
172
+ target held. `sketch new` refuses targets whose `shared/` folders hold one path with different contents. Only the target may import it: a merge refuses while another part of the picked variant
166
173
  does, so move that code into the part. `sketch new` prints each file it puts in `shared/`, for example
167
174
  the `shared/` folder of a merged page: edit it there, as part of the sketch, not in a variant copy.
168
175
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethlete/agent-rules",
3
- "version": "0.1.0-next.28",
3
+ "version": "0.1.0-next.29",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/ethlete-io/ethdk",