@codyswann/lisa 4.81.3 → 4.81.5
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 +52 -14
- 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-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/references/runbook-template.md +4 -3
- 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-queue.mjs +9 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- 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 +36 -9
- 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 +22 -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 +57 -22
- package/plugins/lisa/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/lisa/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +36 -9
- package/plugins/lisa/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +22 -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 +57 -22
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/lisa-agy/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- 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 +36 -9
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +22 -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 +57 -22
- 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/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa-copilot/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/lisa-copilot/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- 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 +36 -9
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +22 -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 +57 -22
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -0
- package/plugins/lisa-cursor/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/lisa-cursor/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- 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 +36 -9
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +22 -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 +57 -22
- package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa-devin/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/lisa-devin/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- 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 +36 -9
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +22 -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 +57 -22
- 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/rules/reference/config-resolution.md +1 -0
- package/plugins/src/base/scripts/ready-queue-order.mjs +34 -14
- package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/src/base/skills/lisa-session-manager/SKILL.md +15 -8
- package/plugins/src/base/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- 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 +36 -9
- package/plugins/src/base/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +22 -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 +57 -22
|
@@ -727,6 +727,35 @@ function findCredentials(value, path) {
|
|
|
727
727
|
);
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Validate `sessionManager.operatorIds`: the people the manager works for,
|
|
732
|
+
* whose assigned items it dispatches first. Matched against the assignee the
|
|
733
|
+
* way "me" is, so the GitHub entry is a login, not the numeric id
|
|
734
|
+
* `trustedAuthorIds` takes. An empty list reads as configured but names no
|
|
735
|
+
* one, so it is refused like an empty `trustedAuthorIds`.
|
|
736
|
+
* @param {unknown} ids The value, if present.
|
|
737
|
+
* @returns {string[]} Problems found.
|
|
738
|
+
*/
|
|
739
|
+
function validateOperatorIds(ids) {
|
|
740
|
+
if (ids === undefined) return [];
|
|
741
|
+
if (
|
|
742
|
+
!Array.isArray(ids) ||
|
|
743
|
+
!ids.every(id => typeof id === "string" && id.trim() !== "")
|
|
744
|
+
) {
|
|
745
|
+
return [
|
|
746
|
+
`sessionManager.operatorIds must be an array of tracker user ids as ` +
|
|
747
|
+
`strings, matched against the assignee: the GitHub login, the Jira ` +
|
|
748
|
+
`accountId or the Linear user id.`,
|
|
749
|
+
];
|
|
750
|
+
}
|
|
751
|
+
return ids.length === 0
|
|
752
|
+
? [
|
|
753
|
+
`sessionManager.operatorIds must name at least one person. Remove ` +
|
|
754
|
+
`the key instead if the manager works only for its own identity.`,
|
|
755
|
+
]
|
|
756
|
+
: [];
|
|
757
|
+
}
|
|
758
|
+
|
|
730
759
|
/**
|
|
731
760
|
* Validate the opt-in `sessionManager` block.
|
|
732
761
|
*
|
|
@@ -786,6 +815,7 @@ export function validateSessionManager(block) {
|
|
|
786
815
|
`if every author's item may be dispatched.`,
|
|
787
816
|
]
|
|
788
817
|
: []),
|
|
818
|
+
...validateOperatorIds(block.operatorIds),
|
|
789
819
|
...findCredentials(block, "sessionManager"),
|
|
790
820
|
];
|
|
791
821
|
}
|
|
@@ -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`
|
|
@@ -66,7 +66,12 @@ the manager.
|
|
|
66
66
|
claims go through `lisa-tracker-claim`. Never call a vendor API around them.
|
|
67
67
|
- **"Me".** Resolve once per manager run and store it with `ledger control --me <id>`: GitHub
|
|
68
68
|
`gh api user --jq .login`; Jira the accountId from the `lisa-atlassian-access` identity probe;
|
|
69
|
-
Linear `viewer.id` through `lisa-linear-access`.
|
|
69
|
+
Linear `viewer.id` through `lisa-linear-access`. "Me" is whoever the tracker credential belongs
|
|
70
|
+
to, which is often an agent account.
|
|
71
|
+
- **Operators.** `sessionManager.operatorIds` names the people the manager works for (the same
|
|
72
|
+
kind of id as "me": GitHub login, Jira accountId, Linear user id). Their assigned items come
|
|
73
|
+
first, then items assigned to "me", then unassigned. Items assigned to anyone else are never
|
|
74
|
+
dispatched. Never treat an operator's item as someone else's.
|
|
70
75
|
- **Workers** start only through `spawn`. The worker prompt is exactly
|
|
71
76
|
`/lisa:implement <REF> managedBy=<manager-name>`; `lisa-implement` owns what `managedBy=` means.
|
|
72
77
|
Never start a worker another way, and never reuse a worker for another item or a new attempt.
|
|
@@ -207,8 +212,8 @@ Skip while `capacity` reports `paused`. Otherwise:
|
|
|
207
212
|
Raw Jira issues carry `key` and `fields` (summary, priority, assignee, created, labels,
|
|
208
213
|
description, reporter, issuetype, subtasks); raw Linear nodes carry identifier, title, priority,
|
|
209
214
|
createdAt, assignee, labels, description, creator and children with their state type.
|
|
210
|
-
- `ordered` is the dispatch order
|
|
211
|
-
needs no action.
|
|
215
|
+
- `ordered` is the dispatch order: assigned to an operator, then to "me", then unassigned; each
|
|
216
|
+
group by priority, then age. `excluded` needs no action.
|
|
212
217
|
2. **While `capacity` says `can_spawn`** and items remain, take the next item that has no pending
|
|
213
218
|
question in the ledger:
|
|
214
219
|
1. Read it fully (`lisa-tracker-read`): title, description, labels, project, parent, comments.
|
|
@@ -223,9 +228,11 @@ Skip while `capacity` reports `paused`. Otherwise:
|
|
|
223
228
|
`cd "$(node "$SM/session-manager.mjs" repo-dir --repo <key> --raw --project-root "$ROOT")"`
|
|
224
229
|
(omit `--repo` on a single-repo project), invoke `lisa-tracker-claim <ref>`, then
|
|
225
230
|
`cd "$ROOT" && pwd` and confirm the path. Continue only on `claimed` or `reused` with the
|
|
226
|
-
assignee equal to "me"
|
|
227
|
-
|
|
228
|
-
|
|
231
|
+
assignee equal to "me" or to one of `sessionManager.operatorIds` (the claim assigns only an
|
|
232
|
+
unassigned item, so an operator's item keeps the operator); anything else means skip. Then
|
|
233
|
+
re-read the item with `lisa-tracker-read`: if the re-read shows an assignee that is neither
|
|
234
|
+
"me" nor an operator, or shows the state did not change to the `claimed` role, skip it
|
|
235
|
+
(someone else holds it, or the claim did not land).
|
|
229
236
|
Never spawn on the claim's word alone.
|
|
230
237
|
4. **Spawn.** `spawn <ref> [--repo <key>]`.
|
|
231
238
|
- exit 0: post `[<REF>] started in <repo> (<name>)` to Telegram.
|
|
@@ -18,9 +18,10 @@ session list from `claude agents`; and the project's own Telegram chat.
|
|
|
18
18
|
|
|
19
19
|
## Candidate selection
|
|
20
20
|
|
|
21
|
-
Leaf items in the `ready` role, ordered: assigned to
|
|
22
|
-
then
|
|
23
|
-
|
|
21
|
+
Leaf items in the `ready` role, ordered: assigned to an operator (`sessionManager.operatorIds`),
|
|
22
|
+
then to the manager's own tracker identity, then unassigned; within each, by priority, then oldest.
|
|
23
|
+
Items assigned to someone else, containers, human-held items and (when configured) items by
|
|
24
|
+
untrusted authors are skipped. As many as the machine has capacity for.
|
|
24
25
|
|
|
25
26
|
## Scope/bounds
|
|
26
27
|
|
|
@@ -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.
|
|
@@ -123,9 +123,11 @@ function readEnvelopeQueue(ctx, tracker) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* `queue --tracker <github|jira|linear>`: the Ready queue,
|
|
126
|
+
* `queue --tracker <github|jira|linear>`: the Ready queue, the operator's
|
|
127
|
+
* items first (`sessionManager.operatorIds`), then mine, then unassigned.
|
|
127
128
|
* @param {object} ctx - CLI context
|
|
128
|
-
* @returns {Promise<{ json: object }>}
|
|
129
|
+
* @returns {Promise<{ json: object }>}
|
|
130
|
+
* `{tracker,me,operators,total,pages,ordered,excluded}`
|
|
129
131
|
*/
|
|
130
132
|
export async function queue(ctx) {
|
|
131
133
|
const tracker = ctx.values.tracker;
|
|
@@ -139,10 +141,14 @@ export async function queue(ctx) {
|
|
|
139
141
|
? fetchGithubQueue(ctx, project)
|
|
140
142
|
: readEnvelopeQueue(ctx, tracker);
|
|
141
143
|
const me = ctx.values.me ?? (await readLedger(ctx.root)).control.me ?? null;
|
|
144
|
+
const operators = config?.operatorIds ?? [];
|
|
142
145
|
const order = orderReadyQueue(items, {
|
|
143
146
|
me,
|
|
147
|
+
operators,
|
|
144
148
|
trustedAuthorIds: config?.trustedAuthorIds ?? undefined,
|
|
145
149
|
humanNeededLabel: humanNeededLabel(project, tracker),
|
|
146
150
|
});
|
|
147
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
json: { tracker, me, operators, total: items.length, pages, ...order },
|
|
153
|
+
};
|
|
148
154
|
}
|
|
@@ -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>]
|
|
@@ -87,7 +111,10 @@ Per cycle
|
|
|
87
111
|
queue --tracker jira|linear --input <file> [--me <id>]
|
|
88
112
|
<file> = {complete:true,pages,items:[raw vendor issues]}; exit 2 unless
|
|
89
113
|
complete is exactly true.
|
|
90
|
-
Both print {tracker,me,total,pages,ordered,excluded:[{ref,
|
|
114
|
+
Both print {tracker,me,operators,total,pages,ordered,excluded:[{ref,
|
|
115
|
+
reason}]}. ordered: items assigned to sessionManager.operatorIds
|
|
116
|
+
first, then to --me, then unassigned; each group by priority, then age.
|
|
117
|
+
Items assigned to anyone else are excluded (assigned-to-other).
|
|
91
118
|
--me defaults to the ledger's control.me.
|
|
92
119
|
ledger get [<ref>]
|
|
93
120
|
ledger record-spawn <ref> --worker <name> [--session-id <id>] [--repo <key>]
|
|
@@ -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
|
+
}
|