@codyswann/lisa 4.81.3 → 4.81.4
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/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +44 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/src/base/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/src/base/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/src/base/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +44 -21
|
@@ -44,11 +44,14 @@ writes `$SM`, and pass `--project-root "$ROOT"` (the absolute project root) on e
|
|
|
44
44
|
|
|
45
45
|
1. Preview: `node "$SM/session-manager.mjs" init-config --project-root "$ROOT"`. It detects
|
|
46
46
|
`projects/*/.lisa.config.json` and proposes `name`, `interval`, `repos`, `capacity`, `stall` and a
|
|
47
|
-
per-project `notify.stateDir`.
|
|
47
|
+
per-project `notify.stateDir`. Repo detection leaves out linked worktrees and keeps one checkout
|
|
48
|
+
per remote, so ticket worktrees and second clones under `projects/` are not proposed. Show the
|
|
49
|
+
block to the operator and ask whether any repo is missing or should not be routed.
|
|
48
50
|
2. Write it: the same command with `--write`. It adds only non-secret fields and never overwrites an
|
|
49
51
|
existing block (`reason: "exists"` means keep it).
|
|
50
|
-
3.
|
|
51
|
-
|
|
52
|
+
3. Each repo's `hints` are a draft: its name plus one per well-known dependency (`NestJS`, `Expo`,
|
|
53
|
+
`AWS CDK`, ...). Ask the operator to correct them and add the areas that identify the repo
|
|
54
|
+
(e.g. `api`: `["NestJS","migrations"]`, `web`: `["Expo","screens"]`), and whether to
|
|
52
55
|
set `sessionManager.trustedAuthorIds`. Without it, any author's Ready item reaches a
|
|
53
56
|
bypass-permissions worker; say so plainly. The list holds stable tracker user ids as strings,
|
|
54
57
|
never logins: the GitHub numeric user id (`gh api users/<login> --jq .id`), the Jira
|
|
@@ -63,8 +66,14 @@ The operator creates a bot with @BotFather, one per project. Then run
|
|
|
63
66
|
- `stored: true`: done.
|
|
64
67
|
- Exit 2 because a token already exists: keep it (re-running setup is safe). Only when the operator
|
|
65
68
|
says the stored token is wrong or belongs to another project, re-run with `store-token --replace`.
|
|
66
|
-
- `reason: "no-tty"` (an agent's Bash has no terminal):
|
|
67
|
-
|
|
69
|
+
- `reason: "no-tty"` (an agent's Bash has no terminal): offer the operator both routes, and say
|
|
70
|
+
plainly that Claude Code's `!` prefix has no terminal either, so it fails the same way:
|
|
71
|
+
- **Clipboard (simplest):** they copy the token from the BotFather chat and tell you; then run
|
|
72
|
+
`clipboardCommand` from the output (`store-token --from-clipboard`). It validates the token,
|
|
73
|
+
stores it, never prints it, and clears the clipboard. On `stored: false`, say what `message`
|
|
74
|
+
reports and ask them to copy it again.
|
|
75
|
+
- **Separate terminal app:** print `command` and ask them to run it in Terminal, iTerm or similar,
|
|
76
|
+
outside the agent, where it prompts with echo off. Wait for them to confirm.
|
|
68
77
|
- `empty` / `invalid`: say what was wrong and ask again.
|
|
69
78
|
|
|
70
79
|
Never ask for the token in chat, never echo, log or commit it, and never put it in
|
|
@@ -99,14 +108,24 @@ If `.lisa/automations/session-manager.runbook.md` does not exist, copy
|
|
|
99
108
|
`$SM/../references/runbook-template.md` there. Never overwrite an existing runbook: it is the
|
|
100
109
|
project's. It is checked in, like every automation runbook (`automation-runbook-contract`).
|
|
101
110
|
|
|
102
|
-
## Step 7:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
## Step 7: Check for competing intake loops
|
|
112
|
+
|
|
113
|
+
The scheduled `intake-repair` loop can re-dispatch an item a local worker is still building (it treats
|
|
114
|
+
a claimed item with no tracker activity for `intake.repair.staleAfterHours` as stalled), and
|
|
115
|
+
`intake-tickets` competes for the same Ready items. Find out whether either is running before you
|
|
116
|
+
start the manager; do not leave the operator to check.
|
|
117
|
+
|
|
118
|
+
1. Run `node "$SM/session-manager.mjs" coexistence --project-root "$ROOT"`. For each loop it reports
|
|
119
|
+
the last run recorded in this project's run log and whether that is `recent`. `live` lists the
|
|
120
|
+
loops that ran recently.
|
|
121
|
+
2. A run log is evidence, not proof: also list your runtime's scheduler for those loop names (Claude
|
|
122
|
+
Code: the `/schedule` listing of claude.ai routines; Codex: automations; a `lisa-<loop>.yml`
|
|
123
|
+
workflow with a `schedule:` trigger on GitHub Actions).
|
|
124
|
+
3. Report what you found, per loop, in one line each. Only for a loop that is live (a recent run, or a
|
|
125
|
+
scheduler entry): recommend tearing down `intake-tickets` (`/lisa:tear-down-automations
|
|
126
|
+
intake-tickets`) and either raising `intake.repair.staleAfterHours` above
|
|
127
|
+
`stall.noOutputMinutes × stall.maxAttempts` or tearing down `intake-repair`. When neither is live,
|
|
128
|
+
say so and move on.
|
|
110
129
|
|
|
111
130
|
## Step 8: Accept workspace trust in each worker repo
|
|
112
131
|
|
|
@@ -115,12 +134,15 @@ folder?" prompt: until someone has accepted it in a directory, `claude --bg` ref
|
|
|
115
134
|
and `spawn` exits 2 saying so. Do this once per routed repo (or once for the root on a single-repo
|
|
116
135
|
project), on every machine that runs the manager:
|
|
117
136
|
|
|
118
|
-
1.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
accept
|
|
137
|
+
1. Run `node "$SM/session-manager.mjs" trust-status --project-root "$ROOT"`. It lists every
|
|
138
|
+
directory workers start in (each routed repo, or the root on a single-repo project) with whether
|
|
139
|
+
Claude Code has already recorded trust for it. `needsTrust` lists the ones that are not
|
|
140
|
+
`trusted: true`. When it is empty, this step is done: say so.
|
|
141
|
+
2. For each directory in `needsTrust`, ask the operator, in a separate terminal app (not the agent's
|
|
142
|
+
Bash or Claude Code's `!` prefix), to run `claude` once in that directory, accept the trust prompt,
|
|
143
|
+
then exit. An agent's Bash cannot answer that prompt, so never try to accept it for them. Then
|
|
144
|
+
re-run `trust-status` to confirm. (`trusted: "unknown"` means Claude Code's state file could not be
|
|
145
|
+
read; treat it as needing trust.)
|
|
124
146
|
3. Re-run this step whenever a repo is added to `sessionManager.repos`.
|
|
125
147
|
|
|
126
148
|
## Step 9: Start and pair
|
|
@@ -154,5 +176,6 @@ confirm the "Session manager Telegram isolation?" check is green. Fix every warn
|
|
|
154
176
|
`node "$SM/session-manager.mjs" status --project-root "$ROOT"` should show the manager running.
|
|
155
177
|
|
|
156
178
|
Report: the config block written (or kept), where the token lives (the path, never the value), the
|
|
157
|
-
plugin scope result, the runbook path, which repo dirs
|
|
158
|
-
tmux session name, and the
|
|
179
|
+
plugin scope result, the runbook path, which repo dirs were already trusted and which the operator
|
|
180
|
+
accepted trust in, what the competing-loop check found, the manager's tmux session name, and the
|
|
181
|
+
doctor result.
|
|
@@ -23,9 +23,9 @@ channels (Telegram) and cross-session messaging (`SendMessage`, `crossSessionInb
|
|
|
23
23
|
| Runtime | lisa-setup-session-manager | lisa-session-manager (manager) | `managedBy=` |
|
|
24
24
|
|---|---|---|---|
|
|
25
25
|
| Claude Code | supported | supported (reference). Needs `claude` ≥ 2.1.224, `tmux`, `bun` | supported |
|
|
26
|
-
| Codex | config, token
|
|
26
|
+
| Codex | config, token (prompt or `--from-clipboard`), runbook, `trust-status` and `coexistence` are supported. `trust-status` reads Claude Code's trust record, which is the one that matters (workers are Claude Code); accepting trust needs the `claude` CLI. `coexistence` sees only run logs, so also list Codex automations. `start` launches a Claude Code manager, so it works when the `claude` CLI is installed | the skill ships, but `cycle` refuses to run here (no `/loop`, channels or `SendMessage`); tell the operator to run `start` | rejected with the gap message |
|
|
27
27
|
| Cursor / Antigravity (agy) / Copilot / Devin | same as Codex | same as Codex | rejected |
|
|
28
|
-
| OpenCode |
|
|
28
|
+
| OpenCode | same setup subcommands as Codex, but script resolution relies on the npm rung: the CLI imports plugin-level scripts the skill-folder copy lacks, so every subcommand needs the npm package | gap | rejected |
|
|
29
29
|
|
|
30
30
|
On any runtime other than Claude Code, `cycle` stops with: "The session manager runs on Claude Code
|
|
31
31
|
only (background sessions, channels, cross-session messaging). Run `/lisa:session-manager start`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Manager
|
|
3
|
-
*
|
|
2
|
+
* Manager subcommands of the session-manager CLI: start, stop, status,
|
|
3
|
+
* repo-dir and validate. The setup ones (init-config, store-token,
|
|
4
|
+
* trust-status, coexistence) live in cli-setup.mjs.
|
|
4
5
|
*
|
|
5
6
|
* `start` hosts the manager in tmux. It refuses (exit 2) unless the bot
|
|
6
7
|
* token file exists with owner-only permissions in the project's own state
|
|
@@ -11,15 +12,11 @@
|
|
|
11
12
|
* manager's tmux session: workers hold claimed work and keep running.
|
|
12
13
|
* @module session-manager/cli-manager
|
|
13
14
|
*/
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
|
|
17
15
|
import {
|
|
18
16
|
CliError,
|
|
19
17
|
EXIT,
|
|
20
18
|
GLOBAL_CONFIG_FILE,
|
|
21
19
|
asSetupError,
|
|
22
|
-
asSetupErrorAsync,
|
|
23
20
|
intFlag,
|
|
24
21
|
listAgents,
|
|
25
22
|
loadProject,
|
|
@@ -28,12 +25,8 @@ import {
|
|
|
28
25
|
requireLisaRepo,
|
|
29
26
|
run,
|
|
30
27
|
} from "./cli-runtime.mjs";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
defaultProjectSlug,
|
|
34
|
-
probeOwnSession,
|
|
35
|
-
sessionSurvives,
|
|
36
|
-
} from "./isolation.mjs";
|
|
28
|
+
import { resolveRepoDir } from "./config.mjs";
|
|
29
|
+
import { probeOwnSession, sessionSurvives } from "./isolation.mjs";
|
|
37
30
|
import {
|
|
38
31
|
assertTokenPresent,
|
|
39
32
|
buildForegroundCommand,
|
|
@@ -41,7 +34,6 @@ import {
|
|
|
41
34
|
buildManagerLaunch,
|
|
42
35
|
buildStopManager,
|
|
43
36
|
resolveStateDir,
|
|
44
|
-
storeToken as storeTokenFile,
|
|
45
37
|
} from "./launch.mjs";
|
|
46
38
|
import { readLedger } from "./ledger.mjs";
|
|
47
39
|
import { parseWorkerName, scrubWorkerEnv } from "./workers.mjs";
|
|
@@ -208,69 +200,6 @@ export function validate(ctx) {
|
|
|
208
200
|
};
|
|
209
201
|
}
|
|
210
202
|
|
|
211
|
-
/**
|
|
212
|
-
* `init-config [--slug <slug>] [--write]`: the default block for this
|
|
213
|
-
* project, written to `.lisa.config.json` only with `--write` and only when
|
|
214
|
-
* no block exists. Non-secret fields only; the token never goes here.
|
|
215
|
-
* @param {object} ctx - CLI context
|
|
216
|
-
* @returns {{ json: object }} `{written,block,...}`
|
|
217
|
-
*/
|
|
218
|
-
export function initConfig(ctx) {
|
|
219
|
-
const { global } = loadProject(ctx.root);
|
|
220
|
-
if (global?.sessionManager) {
|
|
221
|
-
return {
|
|
222
|
-
json: { written: false, reason: "exists", block: global.sessionManager },
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
const slug =
|
|
226
|
-
ctx.values.slug ?? defaultProjectSlug(ctx.root, global?.github?.repo);
|
|
227
|
-
const block = asSetupError(() =>
|
|
228
|
-
buildDefaultBlock({
|
|
229
|
-
projectSlug: slug,
|
|
230
|
-
detectedRepos: detectRepos(ctx.root),
|
|
231
|
-
})
|
|
232
|
-
);
|
|
233
|
-
if (!ctx.values.write) return { json: { written: false, block } };
|
|
234
|
-
|
|
235
|
-
const file = path.join(ctx.root, GLOBAL_CONFIG_FILE);
|
|
236
|
-
if (!existsSync(file)) {
|
|
237
|
-
throw new CliError(
|
|
238
|
-
`${file} does not exist; set up Lisa in this project first.`,
|
|
239
|
-
EXIT.SETUP
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
const current = JSON.parse(readFileSync(file, "utf8"));
|
|
243
|
-
writeFileSync(
|
|
244
|
-
file,
|
|
245
|
-
`${JSON.stringify({ ...current, sessionManager: block }, null, 2)}\n`
|
|
246
|
-
);
|
|
247
|
-
return { json: { written: true, path: file, block } };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* `store-token [--replace]`: prompt for the bot token on the operator's
|
|
252
|
-
* terminal and write `<stateDir>/.env`. An existing token is kept (exit 2)
|
|
253
|
-
* unless `--replace` is given. With no TTY it prints the command the operator
|
|
254
|
-
* must run themselves and exits 0 with `stored:false`.
|
|
255
|
-
* @param {object} ctx - CLI context
|
|
256
|
-
* @returns {Promise<{ json: object, exitCode: number }>} Outcome
|
|
257
|
-
*/
|
|
258
|
-
export async function storeToken(ctx) {
|
|
259
|
-
const config = requireConfig(ctx);
|
|
260
|
-
const result = await asSetupErrorAsync(() =>
|
|
261
|
-
storeTokenFile({
|
|
262
|
-
stateDir: config.notify?.stateDir,
|
|
263
|
-
scriptsDir: ctx.scriptsDir,
|
|
264
|
-
projectRoot: ctx.root,
|
|
265
|
-
replace: ctx.values.replace === true,
|
|
266
|
-
env: ctx.env,
|
|
267
|
-
homedir: ctx.homedir,
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
const refused = result.stored === false && result.reason !== "no-tty";
|
|
271
|
-
return { json: result, exitCode: refused ? EXIT.SETUP : EXIT.OK };
|
|
272
|
-
}
|
|
273
|
-
|
|
274
203
|
/**
|
|
275
204
|
* `status`: is the manager's tmux session up, and what does the ledger say
|
|
276
205
|
* about each worker it dispatched.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup subcommands of the session-manager CLI: init-config, store-token,
|
|
3
|
+
* trust-status and coexistence. Each one answers a question
|
|
4
|
+
* `lisa-setup-session-manager` would otherwise put to the operator, so setup
|
|
5
|
+
* asks only what a script cannot find out.
|
|
6
|
+
* @module session-manager/cli-setup
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
CliError,
|
|
13
|
+
EXIT,
|
|
14
|
+
GLOBAL_CONFIG_FILE,
|
|
15
|
+
asSetupError,
|
|
16
|
+
asSetupErrorAsync,
|
|
17
|
+
loadProject,
|
|
18
|
+
requireConfig,
|
|
19
|
+
} from "./cli-runtime.mjs";
|
|
20
|
+
import {
|
|
21
|
+
buildDefaultBlock,
|
|
22
|
+
detectRepos,
|
|
23
|
+
draftHints,
|
|
24
|
+
resolveRepoDir,
|
|
25
|
+
} from "./config.mjs";
|
|
26
|
+
import { defaultProjectSlug } from "./isolation.mjs";
|
|
27
|
+
import {
|
|
28
|
+
storeToken as storeTokenFile,
|
|
29
|
+
storeTokenFromClipboard,
|
|
30
|
+
} from "./launch.mjs";
|
|
31
|
+
import {
|
|
32
|
+
coexistence as readCoexistence,
|
|
33
|
+
trustStatus as readTrust,
|
|
34
|
+
} from "./setup-checks.mjs";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `init-config [--slug <slug>] [--write]`: the default block for this
|
|
38
|
+
* project, written to `.lisa.config.json` only with `--write` and only when
|
|
39
|
+
* no block exists. Non-secret fields only; the token never goes here. Repos
|
|
40
|
+
* are the routable children `detectRepos` finds (linked worktrees and second
|
|
41
|
+
* clones of one remote left out), each with drafted hints.
|
|
42
|
+
* @param {object} ctx - CLI context
|
|
43
|
+
* @returns {{ json: object }} `{written,block,...}`
|
|
44
|
+
*/
|
|
45
|
+
export function initConfig(ctx) {
|
|
46
|
+
const { global } = loadProject(ctx.root);
|
|
47
|
+
if (global?.sessionManager) {
|
|
48
|
+
return {
|
|
49
|
+
json: { written: false, reason: "exists", block: global.sessionManager },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const slug =
|
|
53
|
+
ctx.values.slug ?? defaultProjectSlug(ctx.root, global?.github?.repo);
|
|
54
|
+
const detectedRepos = detectRepos(ctx.root);
|
|
55
|
+
const hints = Object.fromEntries(
|
|
56
|
+
detectedRepos.map(name => [
|
|
57
|
+
name,
|
|
58
|
+
draftHints(path.join(ctx.root, "projects", name), name),
|
|
59
|
+
])
|
|
60
|
+
);
|
|
61
|
+
const block = asSetupError(() =>
|
|
62
|
+
buildDefaultBlock({ projectSlug: slug, detectedRepos, hints })
|
|
63
|
+
);
|
|
64
|
+
if (!ctx.values.write) return { json: { written: false, block } };
|
|
65
|
+
|
|
66
|
+
const file = path.join(ctx.root, GLOBAL_CONFIG_FILE);
|
|
67
|
+
if (!existsSync(file)) {
|
|
68
|
+
throw new CliError(
|
|
69
|
+
`${file} does not exist; set up Lisa in this project first.`,
|
|
70
|
+
EXIT.SETUP
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const current = JSON.parse(readFileSync(file, "utf8"));
|
|
74
|
+
writeFileSync(
|
|
75
|
+
file,
|
|
76
|
+
`${JSON.stringify({ ...current, sessionManager: block }, null, 2)}\n`
|
|
77
|
+
);
|
|
78
|
+
return { json: { written: true, path: file, block } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `store-token [--replace] [--from-clipboard]`: store the bot token in the
|
|
83
|
+
* project's state dir. By default it prompts on the operator's terminal (echo
|
|
84
|
+
* off); with no TTY it prints the two commands that work instead and exits 0
|
|
85
|
+
* with `stored:false`. `--from-clipboard` reads the system clipboard and needs
|
|
86
|
+
* no TTY. An existing token is kept (exit 2) unless `--replace` is given.
|
|
87
|
+
* @param {object} ctx - CLI context
|
|
88
|
+
* @returns {Promise<{ json: object, exitCode: number }>} Outcome
|
|
89
|
+
*/
|
|
90
|
+
export async function storeToken(ctx) {
|
|
91
|
+
const config = requireConfig(ctx);
|
|
92
|
+
const input = {
|
|
93
|
+
stateDir: config.notify?.stateDir,
|
|
94
|
+
scriptsDir: ctx.scriptsDir,
|
|
95
|
+
projectRoot: ctx.root,
|
|
96
|
+
replace: ctx.values.replace === true,
|
|
97
|
+
env: ctx.env,
|
|
98
|
+
homedir: ctx.homedir,
|
|
99
|
+
};
|
|
100
|
+
const result = ctx.values["from-clipboard"]
|
|
101
|
+
? asSetupError(() => storeTokenFromClipboard(input))
|
|
102
|
+
: await asSetupErrorAsync(() => storeTokenFile(input));
|
|
103
|
+
const refused = result.stored === false && result.reason !== "no-tty";
|
|
104
|
+
return { json: result, exitCode: refused ? EXIT.SETUP : EXIT.OK };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Every directory a worker can start in: each routed repo, or the root on a
|
|
109
|
+
* single-repo project.
|
|
110
|
+
* @param {object} ctx - CLI context
|
|
111
|
+
* @param {object} config - Resolved block
|
|
112
|
+
* @returns {string[]} Absolute directories
|
|
113
|
+
*/
|
|
114
|
+
function workerDirs(ctx, config) {
|
|
115
|
+
const keys = Object.keys(config.repos?.entries ?? {});
|
|
116
|
+
if (keys.length === 0) return [ctx.root];
|
|
117
|
+
return keys.map(key =>
|
|
118
|
+
asSetupError(() => resolveRepoDir(config, key, ctx.root))
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `trust-status`: whether Claude Code has recorded workspace trust for each
|
|
124
|
+
* worker directory. Exit 0 either way; setup asks the operator only about the
|
|
125
|
+
* rows that are not `trusted: true`.
|
|
126
|
+
* @param {object} ctx - CLI context
|
|
127
|
+
* @returns {{ json: object }} `{file,dirs:[{dir,trusted}],needsTrust}`
|
|
128
|
+
*/
|
|
129
|
+
export function trustStatus(ctx) {
|
|
130
|
+
const config = requireConfig(ctx);
|
|
131
|
+
const report = readTrust({
|
|
132
|
+
dirs: workerDirs(ctx, config),
|
|
133
|
+
env: ctx.env,
|
|
134
|
+
homedir: ctx.homedir,
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
json: {
|
|
138
|
+
...report,
|
|
139
|
+
needsTrust: report.dirs
|
|
140
|
+
.filter(row => row.trusted !== true)
|
|
141
|
+
.map(row => row.dir),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* `coexistence`: what this project's run logs say about the scheduled intake
|
|
148
|
+
* loops that compete with the manager. Exit 0 either way.
|
|
149
|
+
* @param {object} ctx - CLI context
|
|
150
|
+
* @returns {{ json: object }} `{windowHours,loops:[...],live}`
|
|
151
|
+
*/
|
|
152
|
+
export function coexistence(ctx) {
|
|
153
|
+
const config = requireConfig(ctx);
|
|
154
|
+
const report = readCoexistence({
|
|
155
|
+
root: ctx.root,
|
|
156
|
+
now: ctx.now(),
|
|
157
|
+
stall: config.stall,
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
json: {
|
|
161
|
+
...report,
|
|
162
|
+
live: report.loops.filter(row => row.recent).map(row => row.loop),
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -61,12 +61,19 @@ Manager
|
|
|
61
61
|
stop tmux kill-session -t =<name> only, and only for a session started
|
|
62
62
|
in this root (exit 2 otherwise); workers keep running.
|
|
63
63
|
stdout {status:"stopped"|"not-running",name}.
|
|
64
|
-
store-token [--replace]
|
|
64
|
+
store-token [--replace] [--from-clipboard]
|
|
65
65
|
Prompts for the bot token on the terminal (echo off) and writes
|
|
66
66
|
<stateDir>/.env. Exit 2 when a token is already there, unless
|
|
67
|
-
--replace. With no TTY
|
|
68
|
-
reason:"no-tty",
|
|
69
|
-
|
|
67
|
+
--replace. With no TTY (an agent's Bash, and Claude Code's !
|
|
68
|
+
prefix too): exit 0, stdout {stored:false,reason:"no-tty",
|
|
69
|
+
message,command,clipboardCommand}; command is the line the
|
|
70
|
+
operator runs in a separate terminal app.
|
|
71
|
+
--from-clipboard reads the token from the clipboard instead
|
|
72
|
+
(pbpaste; wl-paste, xclip or xsel on Linux), needs no TTY, and
|
|
73
|
+
clears the clipboard after a successful store. stdout
|
|
74
|
+
{stored:true,path,clipboardCleared}. The token is never printed.
|
|
75
|
+
Bad input, empty clipboard or no clipboard tool: exit 2,
|
|
76
|
+
{stored:false,reason,message}.
|
|
70
77
|
|
|
71
78
|
Config and routing
|
|
72
79
|
repo-dir [--repo <key>] [--raw]
|
|
@@ -75,10 +82,27 @@ Config and routing
|
|
|
75
82
|
validate stdout {valid,errors}; exit 2 when invalid or absent.
|
|
76
83
|
init-config [--slug <slug>] [--write]
|
|
77
84
|
stdout {written,block[,reason:"exists"][,path]}. Detects
|
|
78
|
-
projects/*/.lisa.config.json
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
projects/*/.lisa.config.json, skipping linked worktrees (.git is
|
|
86
|
+
a file) and keeping one child per origin remote (the one named
|
|
87
|
+
after the repo, else the first by name). Each repo's hints are a
|
|
88
|
+
draft: its name, then one per well-known package.json dependency
|
|
89
|
+
(e.g. @nestjs/* -> NestJS, expo -> Expo, aws-cdk-lib -> AWS CDK).
|
|
90
|
+
Default slug: <github.repo or folder name>-<first 6 hex of
|
|
91
|
+
sha256(realpath(root))>; --slug is used as given. --write adds
|
|
92
|
+
the block to .lisa.config.json, never overwriting an existing one.
|
|
93
|
+
trust-status
|
|
94
|
+
stdout {file,dirs:[{dir,trusted:true|false|"unknown"}],
|
|
95
|
+
needsTrust:[dir]}: whether Claude Code has recorded workspace
|
|
96
|
+
trust (hasTrustDialogAccepted in $CLAUDE_CONFIG_DIR/.claude.json,
|
|
97
|
+
else ~/.claude.json) for each worker dir. "unknown" when that
|
|
98
|
+
file cannot be read. Read-only; never accepts trust.
|
|
99
|
+
coexistence
|
|
100
|
+
stdout {windowHours,loops:[{loop,log,lastRun:{ts,outcome}|null,
|
|
101
|
+
recent}],live:[loop]} for intake-tickets and intake-repair, from
|
|
102
|
+
.lisa/automations/runs/<loop>.jsonl. recent = last run within
|
|
103
|
+
windowHours (2 x stall.noOutputMinutes x stall.maxAttempts, at
|
|
104
|
+
least 24h). Run logs only: the runtime's scheduler is the
|
|
105
|
+
authority on whether a loop is still scheduled.
|
|
82
106
|
|
|
83
107
|
Per cycle
|
|
84
108
|
queue --tracker github [--me <id>] [--label <name>] [--github-repo <o/r>]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read and clear the system clipboard, for `store-token --from-clipboard`.
|
|
3
|
+
*
|
|
4
|
+
* `store-token` prompts on a TTY with echo off, and an agent's Bash has no
|
|
5
|
+
* TTY. Neither does Claude Code's `!` prefix, which is the first thing an
|
|
6
|
+
* operator inside Claude Code tries, so the only working route used to be a
|
|
7
|
+
* separate terminal app (CodySwannGT/lisa#4465). The clipboard is the route an
|
|
8
|
+
* operator reaches for instead: they copy the token from BotFather and the
|
|
9
|
+
* agent stores it without ever seeing it.
|
|
10
|
+
*
|
|
11
|
+
* What is read is a secret. It is returned to the caller and never placed in
|
|
12
|
+
* an error message, a log line or a return value of anything else here.
|
|
13
|
+
* @module session-manager/clipboard
|
|
14
|
+
*/
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
/** Upper bound for one clipboard tool call. */
|
|
18
|
+
const TOOL_TIMEOUT_MS = 5000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clipboard tools per platform, tried in order: the first one that runs is
|
|
22
|
+
* used for both reading and clearing.
|
|
23
|
+
*/
|
|
24
|
+
const TOOLS = Object.freeze({
|
|
25
|
+
darwin: [{ name: "pbpaste", read: ["pbpaste"], clear: ["pbcopy"] }],
|
|
26
|
+
linux: [
|
|
27
|
+
{
|
|
28
|
+
name: "wl-paste",
|
|
29
|
+
read: ["wl-paste", "--no-newline"],
|
|
30
|
+
clear: ["wl-copy", "--clear"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "xclip",
|
|
34
|
+
read: ["xclip", "-o", "-selection", "clipboard"],
|
|
35
|
+
clear: ["xclip", "-i", "-selection", "clipboard"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "xsel",
|
|
39
|
+
read: ["xsel", "--clipboard", "--output"],
|
|
40
|
+
clear: ["xsel", "--clipboard", "--clear"],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run one tool with empty stdin.
|
|
47
|
+
*
|
|
48
|
+
* A clear (`capture: false`) discards the tool's output instead of piping it:
|
|
49
|
+
* `xclip -i` forks a background selection owner, and a child holding an
|
|
50
|
+
* inherited pipe makes `spawnSync` wait out the whole timeout. Any spawn
|
|
51
|
+
* error (a timeout included) is a failure, never a success.
|
|
52
|
+
* @param {string[]} argv - Command and arguments
|
|
53
|
+
* @param {{ capture?: boolean }} [options] - Whether to capture stdout
|
|
54
|
+
* @returns {{ status: number | null, stdout: string, missing: boolean }}
|
|
55
|
+
* Outcome; `missing` when the binary is not on PATH
|
|
56
|
+
*/
|
|
57
|
+
function runTool([command, ...args], { capture = true } = {}) {
|
|
58
|
+
const result = spawnSync(command, args, {
|
|
59
|
+
input: "",
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
timeout: TOOL_TIMEOUT_MS,
|
|
62
|
+
stdio: ["pipe", capture ? "pipe" : "ignore", "ignore"],
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
status: result.error ? null : result.status,
|
|
66
|
+
stdout: result.stdout ?? "",
|
|
67
|
+
missing: result.error?.code === "ENOENT",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read the clipboard with the first available tool for `platform`.
|
|
73
|
+
* @param {{ platform?: string, run?: typeof runTool }} [seams] - Platform and
|
|
74
|
+
* the tool runner
|
|
75
|
+
* @returns {{ ok: true, text: string, clear: () => boolean } |
|
|
76
|
+
* { ok: false, reason: "unsupported" | "unavailable" | "failed",
|
|
77
|
+
* message: string }} The text and a function that clears the clipboard
|
|
78
|
+
* (true when it did), or why nothing was read
|
|
79
|
+
*/
|
|
80
|
+
export function readClipboard(seams = {}) {
|
|
81
|
+
const platform = seams.platform ?? process.platform;
|
|
82
|
+
const run = seams.run ?? runTool;
|
|
83
|
+
const tools = TOOLS[platform];
|
|
84
|
+
if (!tools) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: "unsupported",
|
|
88
|
+
message: `Reading the clipboard is not supported on ${platform}.`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const failed = [];
|
|
92
|
+
for (const tool of tools) {
|
|
93
|
+
const result = run(tool.read);
|
|
94
|
+
if (result.missing) continue;
|
|
95
|
+
// An installed tool can still fail where another works (wl-paste on an
|
|
96
|
+
// X11 session), so a failure moves on to the next tool.
|
|
97
|
+
if (result.status !== 0) {
|
|
98
|
+
failed.push(`${tool.name} exited ${result.status}`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
text: result.stdout,
|
|
104
|
+
clear: () => run(tool.clear, { capture: false }).status === 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (failed.length > 0) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: "failed",
|
|
111
|
+
message: `${failed.join("; ")}; nothing was read.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
reason: "unavailable",
|
|
117
|
+
message: `No clipboard tool found (tried ${tools.map(t => t.name).join(", ")}).`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* registry populates defaults into every project, and the manager starts
|
|
12
12
|
* yolo-mode sessions; it must exist only where an operator wrote it.
|
|
13
13
|
*
|
|
14
|
-
* Pure
|
|
14
|
+
* Pure. Repo detection, which reads the filesystem, lives in repo-detect.mjs
|
|
15
|
+
* and is re-exported here so callers keep one import.
|
|
15
16
|
* @module session-manager/config
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
import {
|
|
19
|
-
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
19
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
mergeConfigLayers as mergeLayers,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "../../lisa-secrets-access/scripts/validate-config.mjs";
|
|
26
26
|
|
|
27
27
|
export { parseLoopInterval, validateSessionManager };
|
|
28
|
+
export { detectRepos, draftHints } from "./repo-detect.mjs";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Documented defaults for every optional key.
|
|
@@ -122,33 +123,6 @@ export function resolveSessionManagerConfig(globalConfig, localConfig) {
|
|
|
122
123
|
};
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
/**
|
|
126
|
-
* List the Lisa-configured child repos of a multi-repo project.
|
|
127
|
-
*
|
|
128
|
-
* A child counts only when it carries its own `.lisa.config.json`: workers run
|
|
129
|
-
* `/lisa:implement` inside it, which needs that file, and a stray directory
|
|
130
|
-
* under `projects/` is not something to route tickets into.
|
|
131
|
-
* @param {string} root Project root.
|
|
132
|
-
* @param {{
|
|
133
|
-
* reposRoot?: string,
|
|
134
|
-
* fs?: { existsSync: (p: string) => boolean,
|
|
135
|
-
* readdirSync: (p: string, o: { withFileTypes: true }) =>
|
|
136
|
-
* Array<{ name: string, isDirectory: () => boolean }> },
|
|
137
|
-
* }} [options] Repos root (default `projects`) and filesystem seam.
|
|
138
|
-
* @returns {string[]} Child directory names, sorted.
|
|
139
|
-
*/
|
|
140
|
-
export function detectRepos(root, options = {}) {
|
|
141
|
-
const fs = options.fs ?? { existsSync, readdirSync };
|
|
142
|
-
const base = join(root, options.reposRoot ?? DEFAULT_REPOS_ROOT);
|
|
143
|
-
if (!fs.existsSync(base)) return [];
|
|
144
|
-
return fs
|
|
145
|
-
.readdirSync(base, { withFileTypes: true })
|
|
146
|
-
.filter(entry => entry.isDirectory() && SAFE_SEGMENT.test(entry.name))
|
|
147
|
-
.filter(entry => fs.existsSync(join(base, entry.name, ".lisa.config.json")))
|
|
148
|
-
.map(entry => entry.name)
|
|
149
|
-
.sort();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
126
|
/**
|
|
153
127
|
* Turn a project name into a slug the validator accepts.
|
|
154
128
|
* @param {string} projectSlug Project name, possibly with dots or capitals.
|
|
@@ -177,13 +151,18 @@ function normalizeSlug(projectSlug) {
|
|
|
177
151
|
* Defaults are written out rather than left implicit, so an operator sees
|
|
178
152
|
* every knob in the file they will edit. With no detected repos the block has
|
|
179
153
|
* no `repos` key, which is what makes a project single-repo. Each detected
|
|
180
|
-
* repo's hints
|
|
181
|
-
*
|
|
182
|
-
* @param {{ projectSlug: string, detectedRepos?: string[]
|
|
183
|
-
* slug
|
|
154
|
+
* repo's hints are its drafted hints when given (`draftHints`), else its own
|
|
155
|
+
* name: valid either way, and setup asks the operator to review them.
|
|
156
|
+
* @param {{ projectSlug: string, detectedRepos?: string[],
|
|
157
|
+
* hints?: Record<string, string[]> }} input Project slug, the output of
|
|
158
|
+
* `detectRepos`, and drafted hints per repo.
|
|
184
159
|
* @returns {SessionManagerBlock} A block `validateSessionManager` accepts.
|
|
185
160
|
*/
|
|
186
|
-
export function buildDefaultBlock({
|
|
161
|
+
export function buildDefaultBlock({
|
|
162
|
+
projectSlug,
|
|
163
|
+
detectedRepos = [],
|
|
164
|
+
hints = {},
|
|
165
|
+
}) {
|
|
187
166
|
const slug = normalizeSlug(projectSlug);
|
|
188
167
|
const repos = [...detectedRepos].sort();
|
|
189
168
|
return {
|
|
@@ -194,7 +173,10 @@ export function buildDefaultBlock({ projectSlug, detectedRepos = [] }) {
|
|
|
194
173
|
repos: {
|
|
195
174
|
root: DEFAULT_REPOS_ROOT,
|
|
196
175
|
entries: Object.fromEntries(
|
|
197
|
-
repos.map(name => [
|
|
176
|
+
repos.map(name => [
|
|
177
|
+
name,
|
|
178
|
+
{ hints: hints[name]?.length ? [...hints[name]] : [name] },
|
|
179
|
+
])
|
|
198
180
|
),
|
|
199
181
|
},
|
|
200
182
|
}
|