mtech-ai-local-runner 0.1.80 → 0.1.81
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/README.md +17 -5
- package/claude-mod/.claude-plugin/plugin.json +1 -1
- package/claude-mod/hooks/hooks.json +18 -0
- package/claude-mod/hooks/register.tsx +5 -3
- package/dist/src/adapters/claude-code/prompt-compiler.js +2 -6
- package/dist/src/adapters/codex/adapter.js +3 -3
- package/dist/src/adapters/codex/prompt-compiler.js +20 -3
- package/dist/src/adapters/local-tool-prompt.js +6 -0
- package/dist/src/asks/claude-mod.js +21 -1
- package/dist/src/asks/commands.js +20 -2
- package/dist/src/cli.js +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -269,7 +269,7 @@ covers it. A stored delivery is forgotten an hour after it arrived, by which tim
|
|
|
269
269
|
long run.
|
|
270
270
|
|
|
271
271
|
Integrations are installed only on request, with `setup --integrations` or `integrations install`; setup never asks
|
|
272
|
-
about them. An app already installed in the directory the environment names is skipped and reported installed, without
|
|
272
|
+
about them. The Work Room pane, below, is the one exception: every setup installs it. An app already installed in the directory the environment names is skipped and reported installed, without
|
|
273
273
|
rewriting its files:
|
|
274
274
|
|
|
275
275
|
```bash
|
|
@@ -298,10 +298,16 @@ your own AI Gateway connector in Claude, and it never starts a model turn: its O
|
|
|
298
298
|
conversation. Without the connector the pane says so. The Claude mobile app draws no input field, so there the pane
|
|
299
299
|
reads, shares and marks read only.
|
|
300
300
|
|
|
301
|
-
The pane
|
|
302
|
-
ships
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
The pane calls the connector with no model turn, so a Claude session in auto mode would refuse every one of those
|
|
302
|
+
calls. The mod therefore ships PreToolUse hooks that match any AI Gateway connector and apply to every caller in the
|
|
303
|
+
session, the model included:
|
|
304
|
+
|
|
305
|
+
- They allow the two reads, `work_room_read` and `work_inbox`.
|
|
306
|
+
- They allow `work_room_receipt_report` only when its `state` is `presented`, the record that the pane showed you a
|
|
307
|
+
message.
|
|
308
|
+
- They ask you in Claude's own permission dialog before every write: `work_room_write`, `work_request_answer`, and
|
|
309
|
+
`work_room_receipt_report` for marking messages read. A write from the pane therefore needs its confirm press and
|
|
310
|
+
then your answer in that dialog.
|
|
305
311
|
|
|
306
312
|
The runner copies the mod to `claude-mod/<version>/` in the state root, without its tests, and installs it with the
|
|
307
313
|
Claude CLI as a local marketplace (`plugin marketplace add` and `plugin install --scope user`). This needs a Claude CLI
|
|
@@ -310,6 +316,12 @@ bundles. Without one, the hooks are installed as before and the result reports t
|
|
|
310
316
|
only a missing or older copy, and it never turns on a mod you turned off. `integrations status` and `doctor` report the
|
|
311
317
|
mod's state under `mod`; `integrations remove claude` and `uninstall` remove it. The daemon never runs the Claude CLI.
|
|
312
318
|
|
|
319
|
+
Every `setup` installs or updates the mod, with or without `--integrations`, and reports it as `workRoomPane`. So
|
|
320
|
+
rerunning `npx mtech-ai-local-runner@<version> setup` is enough to get the newest pane. With `--integrations claude`
|
|
321
|
+
the Claude integration installs it and reports it under its own `mod` instead. After `integrations remove claude`,
|
|
322
|
+
setup leaves the mod out, and its result says so. `integrations install claude` adds the mod again, and from then
|
|
323
|
+
setup keeps it up to date once more. `uninstall` forgets the removal, so a later setup starts fresh.
|
|
324
|
+
|
|
313
325
|
`CLAUDE_CONFIG_DIR` and `CODEX_HOME` are honoured. The LaunchAgent that runs the daemon gets only `PATH`, so every
|
|
314
326
|
install adds the directory it used to a list per app in `integrations.json` in the state root, and the daemon,
|
|
315
327
|
`doctor`, `integrations remove` and `uninstall` look in every recorded directory as well as in the environment. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "name": "ai-gateway-work-room", "version": "1.
|
|
1
|
+
{ "name": "ai-gateway-work-room", "version": "1.2.0", "description": "Work Room shares and requests from colleagues beside the conversation, installed by mtech-ai-local-runner (Work Item 98da2635).", "types": "./types/index.d.ts" }
|
|
@@ -10,6 +10,24 @@
|
|
|
10
10
|
"command": "printf '%s' '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"Work Room pane: reading your own AI Gateway Rooms and requests is allowed.\"}}'"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"matcher": "^mcp__.+__work_room_receipt_report$",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "if grep -Eq '\"state\"[[:space:]]*:[[:space:]]*\"presented\"'; then printf '%s' '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"Work Room pane: recording that it showed you these Room messages is allowed.\"}}'; else printf '%s' '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"Work Room: marking Room messages read tells the AI Gateway you read them. Allow it?\"}}'; fi"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "^mcp__.+__(work_room_write|work_request_answer)$",
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "printf '%s' '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"Work Room: this sends what you wrote to colleagues through the AI Gateway. Allow it?\"}}'"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
13
31
|
}
|
|
14
32
|
]
|
|
15
33
|
}
|
|
@@ -10,9 +10,11 @@ import { bandText, chatPrompt, findPayload, parseActivity, parseInbox, parseMess
|
|
|
10
10
|
* conversation, read and answered through the person's own AI Gateway connector with no model turn. The band above
|
|
11
11
|
* the prompt says what waits; the pane opens only from the band or /work-room, never at session start.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* cannot approve its own calls. So hooks/hooks.json carries
|
|
15
|
-
*
|
|
13
|
+
* Auto mode refuses every call the pane makes, since none comes from a request of the person's in the conversation, and
|
|
14
|
+
* a mod's own module hooks cannot approve its own calls. So hooks/hooks.json carries PreToolUse hooks for every caller
|
|
15
|
+
* (owner decisions 2026-10-31): they allow the reads (work_room_read, work_inbox) and the presented receipt, and they
|
|
16
|
+
* put each write (answer, post, share, mark read) to the person in Claude's own permission dialog, after the pane's
|
|
17
|
+
* confirm press.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
export const PANE = 'work-room'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderBundleCorrectionBrief } from '../../verification/continuation-runner-contract.js';
|
|
2
2
|
import { compilePrompt as compileBrief } from "../prompt-compiler.js";
|
|
3
|
-
import { LOCAL_TOOL_GUIDANCE, repositoryScope } from "../local-tool-prompt.js";
|
|
3
|
+
import { LOCAL_FIND_DISCOVERY, LOCAL_TOOL_GUIDANCE, repositoryScope } from "../local-tool-prompt.js";
|
|
4
4
|
import { localToolNames } from "../../safety/local-tool-policy.js";
|
|
5
5
|
// Claude's working directory is the job's private control directory, so without these rules it sends that directory's
|
|
6
6
|
// absolute path or shell commands to the ai_local tools (P4 Investigate of Work Item 1c8026f1, 2026-10-28). Without the
|
|
@@ -9,11 +9,7 @@ import { localToolNames } from "../../safety/local-tool-policy.js";
|
|
|
9
9
|
const CLAUDE_PATH_DISCOVERY = Object.freeze({
|
|
10
10
|
"local-tools-v1": "local_search searches only inside that one file; there is no directory listing or repository-wide search, so take " +
|
|
11
11
|
"file paths from the objective and from files you have read. ",
|
|
12
|
-
"local-tools-v2":
|
|
13
|
-
"its path, such as MigrateChannelLedgerCommand, or text for a literal it contains, such as migrate:channel-ledger, then " +
|
|
14
|
-
"local_search and local_read on the file found. local_find's path is an optional directory inside the approved scope, not a " +
|
|
15
|
-
"file; it skips vendor/ and node_modules/ unless that directory is inside them, and truncated: true means it stopped at a " +
|
|
16
|
-
"bound, so narrow path, name or text. "
|
|
12
|
+
"local-tools-v2": LOCAL_FIND_DISCOVERY
|
|
17
13
|
});
|
|
18
14
|
const claudePathRules = (inventory) => "## Local tool path rules\n" +
|
|
19
15
|
"Every path argument is one checkout-relative file path inside the approved scope, without a leading slash: write " +
|
|
@@ -6,7 +6,7 @@ import { createJobSecurityReceipt } from '../../safety/job-security-receipt.js';
|
|
|
6
6
|
import { requireJobSecurity } from '../../safety/job-security-policy.js';
|
|
7
7
|
import { CODEX_UNSUPPORTED_DIAGNOSTIC } from "./compatibility.js";
|
|
8
8
|
import { buildCodexInvocation } from "./config-builder.js";
|
|
9
|
-
import { compileCodexPrompt, CODEX_REPORT_SCHEMA } from "./prompt-compiler.js";
|
|
9
|
+
import { codexPromptToolInventory, compileCodexPrompt, CODEX_REPORT_SCHEMA } from "./prompt-compiler.js";
|
|
10
10
|
import { CodexStreamParser } from "./stream-parser.js";
|
|
11
11
|
function object(value) {
|
|
12
12
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -62,7 +62,7 @@ export class CodexAdapter {
|
|
|
62
62
|
this.usingOfficialExec = true;
|
|
63
63
|
let stage = 'codex_prompt';
|
|
64
64
|
try {
|
|
65
|
-
const prompt = compileCodexPrompt(object(ctx.taskPackage), this.options.jobId);
|
|
65
|
+
const prompt = compileCodexPrompt(object(ctx.taskPackage), this.options.jobId, codexPromptToolInventory(approved.security_requirements));
|
|
66
66
|
stage = 'codex_launch';
|
|
67
67
|
this.handle = await this.ownedExec.start(ctx, this.options.jobId, prompt, this.startupAbort.signal);
|
|
68
68
|
this.receipt = this.ownedExec.receipt();
|
|
@@ -144,7 +144,7 @@ export class CodexAdapter {
|
|
|
144
144
|
}
|
|
145
145
|
};
|
|
146
146
|
try {
|
|
147
|
-
const prompt = compileCodexPrompt(pkg, this.options.jobId);
|
|
147
|
+
const prompt = compileCodexPrompt(pkg, this.options.jobId, codexPromptToolInventory(approved.security_requirements));
|
|
148
148
|
this.tools = this.options.createTools(pkg, approved);
|
|
149
149
|
const endpoint = await this.tools.start();
|
|
150
150
|
this.assertNotCancelled();
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import { renderBundleCorrectionBrief } from '../../verification/continuation-runner-contract.js';
|
|
2
2
|
import { compilePrompt as compileBrief } from "../prompt-compiler.js";
|
|
3
|
-
import { LOCAL_TOOL_GUIDANCE, repositoryScope } from "../local-tool-prompt.js";
|
|
4
|
-
|
|
3
|
+
import { LOCAL_FIND_DISCOVERY, LOCAL_TOOL_GUIDANCE, repositoryScope } from "../local-tool-prompt.js";
|
|
4
|
+
import { localToolNames } from "../../safety/local-tool-policy.js";
|
|
5
|
+
import { localToolInventoryFor } from "../../safety/local-tool-server.js";
|
|
6
|
+
import { securityRequirementsFingerprint } from "../../safety/security-requirements.js";
|
|
7
|
+
// local-tools-v1 has no discovery tool and its prompt is unchanged. With local-tools-v2 Codex must be told about local_find:
|
|
8
|
+
// without it, the CHANNEL-4K Investigate of 2026-10-31 (job 4cec57c0) searched directories with local_search and read nothing.
|
|
9
|
+
const CODEX_FILE_DISCOVERY = Object.freeze({
|
|
10
|
+
"local-tools-v1": "",
|
|
11
|
+
"local-tools-v2": `\n\n## Local file discovery\n${LOCAL_FIND_DISCOVERY.trimEnd()}`
|
|
12
|
+
});
|
|
13
|
+
/** The inventory the job's frozen descriptor names (exec-preflight.ts checks the same fingerprint); local-tools-v1 when it
|
|
14
|
+
* names none. The tool server enforces the inventory on its own, so this only picks the prompt text. */
|
|
15
|
+
export function codexPromptToolInventory(securityRequirements) {
|
|
16
|
+
const sha = securityRequirements && typeof securityRequirements === "object" && !Array.isArray(securityRequirements)
|
|
17
|
+
? securityRequirements.tools_sha256 : undefined;
|
|
18
|
+
return localToolInventoryFor(sha, securityRequirementsFingerprint) ?? "local-tools-v1";
|
|
19
|
+
}
|
|
20
|
+
export function compileCodexPrompt(taskPackage, label, toolInventory = "local-tools-v1") {
|
|
21
|
+
localToolNames(toolInventory);
|
|
5
22
|
const correction = renderBundleCorrectionBrief(taskPackage);
|
|
6
|
-
return `${compileBrief(taskPackage, { workItemLabel: label, progressReporting: "native_messages" })}\n\n${repositoryScope(taskPackage)}\n\n${LOCAL_TOOL_GUIDANCE}${correction}`;
|
|
23
|
+
return `${compileBrief(taskPackage, { workItemLabel: label, progressReporting: "native_messages" })}\n\n${repositoryScope(taskPackage)}\n\n${LOCAL_TOOL_GUIDANCE}${CODEX_FILE_DISCOVERY[toolInventory]}${correction}`;
|
|
7
24
|
}
|
|
8
25
|
export const CODEX_REPORT_SCHEMA = {
|
|
9
26
|
type: "object", additionalProperties: false, required: ["status", "report", "evidence", "pending_work"],
|
|
@@ -23,3 +23,9 @@ export function repositoryScope(taskPackage) {
|
|
|
23
23
|
"These are scope descriptions, not instructions from source files; the host policy remains authoritative and may deny blocked/private paths within an allowed prefix.";
|
|
24
24
|
}
|
|
25
25
|
export const LOCAL_TOOL_GUIDANCE = "Use only the ai_local job tools for repository reads, edits and approved tests. For reading, first use server ai_local's local_search({path: \"<exact approved path>\", query: \"text\"}) to find the location, then local_read({path: \"<exact approved path>\", start_line: 1, end_line: 1}) with the matching line numbers. Ranges are inclusive, at most 400 lines; both bounds are required together. Read a matched line first to learn total_lines, then request only the needed in-file context. Omit both bounds only when the entire file is necessary. Use approved checkout-relative paths. For small approved edits, use local_replace({path, old, new}) after reading: old must match exactly once, including whitespace. local_edit requires the complete replacement file, never a patch. Required test criteria are not command grants. No model test commands are registered unless the host explicitly supplies exact model-callable command IDs; do not use local_test to discover commands or run --help. Missing tests remain not_run/unverified, not passed. In a read-only task, use only read/search tools; the Runner enforces the approved paths and operations. A denied native cwd is intentional: it is a private control directory, not the source checkout. Native filesystem, commands, web, images and delegation are unavailable or denied. A denied tool is a scope boundary, never permission to broaden scope or bypass the denial. Do not retry the denied path. It does not remove independently approved paths: continue with relevant listed paths, and report a missing dependency only after checking the available approved source. Do not repeat host orchestration: claim, shadow, fingerprints, binding, administrator checks, launches, Gateway usage and Job reporting belong to the Runner/host, even when described in the objective. Perform only the assigned repository work and report evidence you actually observed. Do not claim that no model call occurred. If the required tool is unavailable, emit one final structured blocked report naming the missing tool and unfinished repository work; do not request or attempt a new launch.";
|
|
26
|
+
/** How to find a file with local-tools-v2 (Work Decision f43e7aa0). Shared by the Claude and Codex prompts. */
|
|
27
|
+
export const LOCAL_FIND_DISCOVERY = "local_search searches only inside that one file. To find a file, use local_find with name for a literal part of " +
|
|
28
|
+
"its path, such as MigrateChannelLedgerCommand, or text for a literal it contains, such as migrate:channel-ledger, then " +
|
|
29
|
+
"local_search and local_read on the file found. local_find's path is an optional directory inside the approved scope, not a " +
|
|
30
|
+
"file; it skips vendor/ and node_modules/ unless that directory is inside them, and truncated: true means it stopped at a " +
|
|
31
|
+
"bound, so narrow path, name or text. ";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
|
-
import { access, cp, mkdir, readdir, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { access, cp, mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, delimiter, isAbsolute, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { packageVersion } from "../package-version.js";
|
|
@@ -13,8 +13,14 @@ import { packageVersion } from "../package-version.js";
|
|
|
13
13
|
* Only a CLI that loads `modules` hooks can install it. On 2026-10-30 the terminal `claude` 2.1.212 could not, and the
|
|
14
14
|
* desktop-bundled 2.1.299 and 2.1.308 could; the desktop app bundles one even when the terminal `claude` is older.
|
|
15
15
|
* A mod the person disabled stays disabled: setup reinstalls only a missing or older one.
|
|
16
|
+
*
|
|
17
|
+
* Every setup installs the mod, with or without --integrations (owner decision 2026-10-31), unless the person removed
|
|
18
|
+
* it with `integrations remove claude`. That removal leaves <stateRoot>/claude-mod-removed, which
|
|
19
|
+
* `integrations install claude` and uninstall delete again.
|
|
16
20
|
*/
|
|
17
21
|
export const CLAUDE_MOD_NAME = "ai-gateway-work-room";
|
|
22
|
+
/** <stateRoot>/claude-mod-removed: the person removed the mod, so setup leaves it out until they install it again. */
|
|
23
|
+
export const CLAUDE_MOD_REMOVED_FILE = "claude-mod-removed";
|
|
18
24
|
export const CLAUDE_MOD_MARKETPLACE = "mtec-ai-gateway";
|
|
19
25
|
const ID = `${CLAUDE_MOD_NAME}@${CLAUDE_MOD_MARKETPLACE}`;
|
|
20
26
|
const MIN_VERSION = [2, 1, 299];
|
|
@@ -126,6 +132,20 @@ export async function removeClaudeMod(deps) {
|
|
|
126
132
|
await rm(join(deps.stateRoot, "claude-mod"), { recursive: true, force: true }).catch(() => undefined);
|
|
127
133
|
return { state: "removed", ...(cli ? { cli } : {}) };
|
|
128
134
|
}
|
|
135
|
+
/** Whether the person removed the mod with `integrations remove claude` since it was last installed on request. */
|
|
136
|
+
export async function claudeModRemovedByPerson(stateRoot) {
|
|
137
|
+
return stat(join(stateRoot, CLAUDE_MOD_REMOVED_FILE)).then(entry => entry.isFile(), () => false);
|
|
138
|
+
}
|
|
139
|
+
/** Records or clears the person's removal. */
|
|
140
|
+
export async function setClaudeModRemovedByPerson(stateRoot, removed) {
|
|
141
|
+
const file = join(stateRoot, CLAUDE_MOD_REMOVED_FILE);
|
|
142
|
+
if (!removed) {
|
|
143
|
+
await rm(file, { force: true });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await mkdir(stateRoot, { recursive: true });
|
|
147
|
+
await writeFile(file, "Removed with integrations remove claude. Run integrations install claude to add the Work Room pane again.\n", { mode: 0o600 });
|
|
148
|
+
}
|
|
129
149
|
/** What `plugin list` says of the mod. Never throws. */
|
|
130
150
|
export async function claudeModStatus(deps) {
|
|
131
151
|
const cli = await findClaudeCli(deps);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ASKS_FILE } from "./store.js";
|
|
4
|
-
import { claudeModDeps, claudeModStatus, installClaudeMod, removeClaudeMod } from "./claude-mod.js";
|
|
4
|
+
import { claudeModDeps, claudeModRemovedByPerson, claudeModStatus, installClaudeMod, removeClaudeMod, setClaudeModRemovedByPerson } from "./claude-mod.js";
|
|
5
5
|
import { homeDirectory, installIntegration, integrationStatus, removeIntegration, removeShimIfUnused } from "./integrations.js";
|
|
6
6
|
import { APPS, isApp } from "./types.js";
|
|
7
7
|
import { deleteGuideState } from "../guide/files.js";
|
|
@@ -89,6 +89,16 @@ async function claudeMod(env, stateRoot, act) {
|
|
|
89
89
|
return { state: "failed", reason: errorCode(error) };
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* setup's Work Room mod: installed on every setup, with or without --integrations (owner decision 2026-10-31), unless
|
|
94
|
+
* the person removed it with `integrations remove claude`. Never throws.
|
|
95
|
+
*/
|
|
96
|
+
export async function setupClaudeMod(env, stateRoot) {
|
|
97
|
+
if (await claudeModRemovedByPerson(stateRoot)) {
|
|
98
|
+
return { state: "skipped", reason: "removed with integrations remove claude; run integrations install claude to add it again" };
|
|
99
|
+
}
|
|
100
|
+
return claudeMod(env, stateRoot, installClaudeMod);
|
|
101
|
+
}
|
|
92
102
|
/** The Work Room mod's state, for integrations status and doctor. It runs the Claude CLI, so the daemon never calls it. */
|
|
93
103
|
export const claudeModStatusOf = (env, stateRoot) => claudeMod(env, stateRoot, claudeModStatus);
|
|
94
104
|
async function attempt(app, env, stateRoot, change) {
|
|
@@ -104,7 +114,10 @@ export async function installIntegrations(apps, env, stateRoot) {
|
|
|
104
114
|
for (const app of apps) {
|
|
105
115
|
const result = await attempt(app, env, stateRoot, () => installIntegration(app, env, stateRoot));
|
|
106
116
|
const done = result.error === undefined ? { ...result, action: NEXT_STEP[app] } : result;
|
|
107
|
-
// The hooks first: they work without the mod, and a Claude CLI too old for the mod leaves them in place.
|
|
117
|
+
// The hooks first: they work without the mod, and a Claude CLI too old for the mod leaves them in place. Installing
|
|
118
|
+
// on request undoes an earlier removal, so setup keeps the mod again.
|
|
119
|
+
if (app === "claude")
|
|
120
|
+
await setClaudeModRemovedByPerson(stateRoot, false).catch(() => undefined);
|
|
108
121
|
results.push(app === "claude" ? { ...done, mod: await claudeMod(env, stateRoot, installClaudeMod) } : done);
|
|
109
122
|
}
|
|
110
123
|
return results;
|
|
@@ -163,6 +176,8 @@ export async function removeAsksInstall(env, stateRoot) {
|
|
|
163
176
|
const results = [];
|
|
164
177
|
for (const app of APPS)
|
|
165
178
|
results.push(await removeAsks(app, env, stateRoot));
|
|
179
|
+
// Uninstall removes everything, so a later setup starts fresh and installs the Work Room mod again.
|
|
180
|
+
await setClaudeModRemovedByPerson(stateRoot, false).catch(() => undefined);
|
|
166
181
|
// A hook left behind (an unreadable Codex hooks file) keeps its shim, which then prints nothing and exits 0.
|
|
167
182
|
await removeShimIfUnused(env, stateRoot).catch(() => false);
|
|
168
183
|
await rm(join(stateRoot, ASKS_FILE), { force: true });
|
|
@@ -187,6 +202,9 @@ export async function runIntegrationsCommand(argv, io) {
|
|
|
187
202
|
else if (parsed.action === "remove") {
|
|
188
203
|
for (const app of parsed.apps)
|
|
189
204
|
results.push(await removeAsks(app, io.env, io.stateRoot));
|
|
205
|
+
// The person removed the Work Room mod: setup leaves it out until integrations install claude.
|
|
206
|
+
if (parsed.apps.includes("claude"))
|
|
207
|
+
await setClaudeModRemovedByPerson(io.stateRoot, true).catch(() => undefined);
|
|
190
208
|
await removeShimIfUnused(io.env, io.stateRoot).catch(() => false);
|
|
191
209
|
}
|
|
192
210
|
else {
|
package/dist/src/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ import { verifyPackagedHook } from "./safety/hook-command.js";
|
|
|
32
32
|
import { access, mkdir, rm } from "node:fs/promises";
|
|
33
33
|
import { buildMessengerDaemon } from "./asks/messenger.js";
|
|
34
34
|
import { runHookClient } from "./asks/hook-client.js";
|
|
35
|
-
import { installGuideIntegrations, installIntegrations, parseFeatures, parseSetupIntegrations, removeAsksInstall, removeGuideInstall, runIntegrationsCommand } from "./asks/commands.js";
|
|
35
|
+
import { installGuideIntegrations, installIntegrations, parseFeatures, parseSetupIntegrations, removeAsksInstall, removeGuideInstall, runIntegrationsCommand, setupClaudeMod } from "./asks/commands.js";
|
|
36
36
|
import { asksDoctorReport } from "./asks/doctor.js";
|
|
37
37
|
import { guideDoctorReport, guideReportShown } from "./guide/doctor.js";
|
|
38
38
|
import { runClaudeReleaseCommand } from "./commands/claude-release.js";
|
|
@@ -296,12 +296,14 @@ export async function main(argv, env, io = {}) {
|
|
|
296
296
|
const features = requestedFeatures ?? ["asks"];
|
|
297
297
|
const integrations = features.includes("asks") ? await installIntegrations(requestedIntegrations ?? [], env, config.stateRoot) : [];
|
|
298
298
|
const guide = features.includes("guide") ? await installGuideIntegrations(requestedIntegrations ?? [], env, config.stateRoot) : undefined;
|
|
299
|
+
// The Work Room pane goes with every setup (owner decision 2026-10-31); --integrations claude has just installed it.
|
|
300
|
+
const workRoomPane = integrations.some(report => report.app === "claude" && report.mod !== undefined) ? undefined : await setupClaudeMod(env, config.stateRoot);
|
|
299
301
|
// The LaunchAgent runs a copy under releases/<version>, never the npx cache that may have run setup.
|
|
300
302
|
const daemonExecutable = await (io.installRelease ?? installStableRelease)({ entry: process.argv[1], stateRoot: config.stateRoot });
|
|
301
303
|
await launchd.install(daemonExecutable, config.stateRoot);
|
|
302
304
|
stdout(JSON.stringify(mode === "full"
|
|
303
|
-
? { configured: true, mode, runnerId: registered.runner.id, adapters, integrations, ...(guide ? { guide } : {}), daemonExecutable }
|
|
304
|
-
: { configured: true, mode, runnerId: registered.runner.id, adapters: adapterDiagnosticsWithRemedies(detected), integrations, ...(guide ? { guide } : {}), daemonExecutable,
|
|
305
|
+
? { configured: true, mode, runnerId: registered.runner.id, adapters, integrations, ...(guide ? { guide } : {}), ...(workRoomPane ? { workRoomPane } : {}), daemonExecutable }
|
|
306
|
+
: { configured: true, mode, runnerId: registered.runner.id, adapters: adapterDiagnosticsWithRemedies(detected), integrations, ...(guide ? { guide } : {}), ...(workRoomPane ? { workRoomPane } : {}), daemonExecutable,
|
|
305
307
|
action: "No executor is admitted on this Mac, so the runner is set up as a messenger: it delivers AI Gateway asks and opens sessions, and runs no jobs. Resolve the adapter diagnostics, then rerun setup to run jobs." }));
|
|
306
308
|
return 0;
|
|
307
309
|
}
|