agents-can-communicate 0.10.4 → 0.11.1
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 +29 -21
- package/bin/acc-indicator.mjs +5 -0
- package/bin/entrypoints/acc-antigravity-relay.mjs +2 -2
- package/bin/entrypoints/acc-hook.mjs +4 -24
- package/bin/entrypoints/acc-mcp.mjs +8 -6
- package/bin/entrypoints/acc.mjs +22 -11
- package/bin/entrypoints/antigravity-relay-binding.mjs +1 -1
- package/docs/CAPABILITIES.md +3 -0
- package/docs/CLI.md +34 -7
- package/docs/MCP.md +42 -2
- package/docs/PROTOCOL.md +19 -0
- package/docs/STATUS_INDICATOR.md +133 -0
- package/docs/TROUBLESHOOTING.md +2 -2
- package/docs/WHY_ACC.md +14 -8
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +3 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/indicator-install.mjs +80 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/indicator-render.mjs +8 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +7 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/hooks/indicator.mjs +83 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +16 -5
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +4 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/indicator-install.mjs +30 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/indicator.mjs +23 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +9 -5
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +4 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/indicator-install.mjs +30 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/indicator.mjs +17 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +12 -6
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +5 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/indicator-command.mjs +14 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/indicator-text.mjs +13 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt-writer.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/offer-observation-order.mjs +12 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/offer-observation-writer.mjs +37 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/offer-observation.mjs +47 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/process-table.mjs +40 -27
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-indicator.mjs +141 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-scan.mjs +99 -0
- package/node_modules/@agents-can-communicate/cli/package.json +8 -1
- package/node_modules/@agents-can-communicate/cli/src/adapter-catalog.mjs +33 -0
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +5 -3
- package/node_modules/@agents-can-communicate/cli/src/claim-command.mjs +64 -0
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +17 -11
- package/node_modules/@agents-can-communicate/cli/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator-command.mjs +65 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator-previous-command.mjs +84 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator-previous-windows.mjs +8 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator.mjs +112 -0
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +4 -0
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +38 -110
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +19 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +5 -3
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/recorded-text.mjs +32 -0
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/claim-selectors.mjs +62 -0
- package/node_modules/@agents-can-communicate/core/src/claims.mjs +88 -81
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +2 -1
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +5 -2
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +2 -1
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +19 -7
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +23 -11
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +66 -26
- package/node_modules/@agents-can-communicate/installer/package.json +3 -2
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +3 -0
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +2 -1
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +11 -2
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/claim-tools.mjs +56 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/input-validator.mjs +9 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +7 -8
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +27 -9
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +3 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/session-read.mjs +13 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/state-reads.mjs +24 -7
- package/package.json +3 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {configureTomlIndicator,indicatorCommand,compareVersionOrder,versionOrder} from '@agents-can-communicate/adapter-sdk';
|
|
4
|
+
import {grokIndicator} from './indicator.mjs';
|
|
5
|
+
|
|
6
|
+
export const grokIndicatorPaths = context => {
|
|
7
|
+
const root=context.grokHome??path.join(context.home,'.grok');
|
|
8
|
+
return {file:path.join(root,'config.toml'),marker:path.join(context.dataHome??root,'acc','adapter-grok','indicator.json'),
|
|
9
|
+
shim:path.join(root,'hooks','acc-indicator.mjs')};
|
|
10
|
+
};
|
|
11
|
+
export async function configureGrokIndicator(context,enabled) {
|
|
12
|
+
const paths=grokIndicatorPaths(context);
|
|
13
|
+
|
|
14
|
+
if(enabled&&!supportsGrokIndicator(context))return [];
|
|
15
|
+
return configureTomlIndicator({...paths,table:['ui','status_line'],enabled,set:async()=>({type:'command',command:enabled?await indicatorCommand({context,adapterId:'grok',...paths,shell:'portable'}):''}),defaults:{refresh_interval:1}});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const preflightGrokIndicator = context => configureTomlIndicator({...grokIndicatorPaths(context),table:['ui','status_line'],enabled:false,dryRun:true});
|
|
19
|
+
|
|
20
|
+
function supportsGrokIndicator(context) {
|
|
21
|
+
return typeof context.clientVersion === "string"
|
|
22
|
+
&& compareVersionOrder(versionOrder(context.clientVersion), versionOrder(grokIndicator.minimumVersion)) >= 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function planGrokIndicator(context) {
|
|
26
|
+
const paths = grokIndicatorPaths(context);
|
|
27
|
+
if (context.indicator === "on" ? !supportsGrokIndicator(context) : !existsSync(paths.marker)) return [];
|
|
28
|
+
return [{path: paths.file, kind: "merge"},
|
|
29
|
+
...(context.indicator === "on" && context.hostPlatform === "win32" ? [{path: paths.shim, kind: "tree"}] : [])];
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {indicatorGlyph,footerText} from '@agents-can-communicate/adapter-sdk';
|
|
2
|
+
|
|
3
|
+
export const grokIndicator = {
|
|
4
|
+
minimumVersion:'1.0.46', reload:'start a new Grok session',
|
|
5
|
+
nativeSessionId: payload=>payload.session_id,
|
|
6
|
+
renderText:indicatorGlyph,
|
|
7
|
+
composeText({payload={},indicator,previous='',settings={}}) {
|
|
8
|
+
const cwd=footerText(payload.workspace?.current_dir??payload.cwd).split(/[\\/]/).filter(Boolean).at(-1);
|
|
9
|
+
const percent=payload.context_window?.used_percentage;
|
|
10
|
+
const cost=payload.cost?.total_cost_usd;
|
|
11
|
+
const started=payload.turn?.started_at_ms;
|
|
12
|
+
const elapsed=Number.isFinite(started)?Date.now()-started:NaN;
|
|
13
|
+
const slots={cwd,model:footerText(payload.model?.display_name??payload.model?.id),
|
|
14
|
+
context:Number.isFinite(percent)?`${Math.round(percent)}% ctx`:'',
|
|
15
|
+
cost:Number.isFinite(cost)&&cost>=0.005?`$${cost.toFixed(2)}`:'',
|
|
16
|
+
'turn-timer':Number.isFinite(elapsed)&&elapsed>=1000?`${Math.floor(elapsed/1000)}s`:'',
|
|
17
|
+
'session-name':footerText(payload.session_name)};
|
|
18
|
+
const items=Array.isArray(settings.items)?settings.items:['cwd','model','context'];
|
|
19
|
+
const base=previous.trimEnd() || items.map(id=>slots[id]).filter(Boolean).join(' │ ');
|
|
20
|
+
const [first,...rest]=base.split(/\r?\n/);
|
|
21
|
+
return [[indicator,first].filter(Boolean).join(' │ '),...rest].join('\n');
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {configureGrokIndicator, planGrokIndicator} from "./indicator-install.mjs";
|
|
1
2
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -64,9 +65,10 @@ const withShim = (wiring, shim, windowsNode = null) => ({
|
|
|
64
65
|
}))])),
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
export async function installGrokHooks({
|
|
68
|
-
hostPlatform = process.platform
|
|
68
|
+
export async function installGrokHooks(context) {
|
|
69
|
+
const {grokHome, home, runner, cli, node, hostPlatform = process.platform} = context;
|
|
69
70
|
const root = grokHome ?? grokHomeOf({ home, grokHome });
|
|
71
|
+
const indicatorChanges = await configureGrokIndicator(context, context.indicator === "on");
|
|
70
72
|
const template = await readJson(path.join(bundle, "hooks", "hooks.json"), { hooks: {} });
|
|
71
73
|
const shim = await writeHookShim({ dir: path.join(root, "hooks"), adapterId: "grok",
|
|
72
74
|
runner, node, name: SHIM_NAME, platform: hostPlatform });
|
|
@@ -80,12 +82,13 @@ export async function installGrokHooks({ grokHome, home, runner, cli, node,
|
|
|
80
82
|
|
|
81
83
|
await writeJson(hooksFile(root), withShim(template, shim,
|
|
82
84
|
hostPlatform === "win32" ? node ?? process.execPath : null));
|
|
83
|
-
return { ok: true, changes: [hooksFile(root), shim, skills], diagnostics: [] };
|
|
85
|
+
return { ok: true, changes: [hooksFile(root), shim, skills, ...indicatorChanges], diagnostics: [] };
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
export async function uninstallGrokHooks(
|
|
88
|
+
export async function uninstallGrokHooks(context) {
|
|
89
|
+
const {grokHome, home, keep = [], hostPlatform} = context;
|
|
87
90
|
const root = grokHome ?? grokHomeOf({ home, grokHome });
|
|
88
|
-
const changes =
|
|
91
|
+
const changes = await configureGrokIndicator(context, false);
|
|
89
92
|
for (const target of [hooksFile(root), shimPath(root, hostPlatform), skillPath(root)]) {
|
|
90
93
|
if (await removeInstalledTree(target, keep)) changes.push(target);
|
|
91
94
|
}
|
|
@@ -106,6 +109,7 @@ export async function detectGrok({ grokHome, home }) {
|
|
|
106
109
|
export function planGrokInstall(context) {
|
|
107
110
|
const root = grokHomeOf(context);
|
|
108
111
|
return [
|
|
112
|
+
...planGrokIndicator(context),
|
|
109
113
|
{ path: hooksFile(root), kind: "tree" },
|
|
110
114
|
{ path: shimPath(root, context.hostPlatform), kind: "tree" },
|
|
111
115
|
{ path: skillPath(root), kind: "tree" },
|
|
@@ -57,19 +57,43 @@ Do this once, not every turn. Update it only when the scope or mode materially
|
|
|
57
57
|
changes. `--hint` is important: it lets ACC match your plan against a peer's
|
|
58
58
|
claim. Intent is awareness, not permission.
|
|
59
59
|
|
|
60
|
-
Before changing shared files, claim the smallest useful
|
|
60
|
+
Before changing shared files, claim the smallest useful resources in one operation:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
{{ACC}} claim --resource 'file:packages/core/**'
|
|
63
|
+
{{ACC}} claim --resource 'file:packages/core/**' \
|
|
64
|
+
--resource 'file:packages/protocol/**' --reason "porting the claim model"
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
Repeat `--resource` for an atomic batch, or supply it once for one claim.
|
|
68
|
+
Shared mode, enforcement, reason, and lease apply to every resource. Duplicate resources after
|
|
69
|
+
normalization are rejected. Exit 5 means a conflict: no claim is created or
|
|
70
|
+
renewed. Batch `details.conflicts` names blocked resources and their holders.
|
|
71
|
+
Narrow your scope, contact the owner, or ask the human. Repeating a successful
|
|
72
|
+
claim command renews your existing claims on those resources.
|
|
73
|
+
|
|
74
|
+
Release selected claims when useful:
|
|
68
75
|
|
|
69
76
|
```bash
|
|
70
|
-
{{ACC}} release --resource 'file:packages/core/**'
|
|
77
|
+
{{ACC}} release --resource 'file:packages/core/**' \
|
|
78
|
+
--resource 'file:packages/protocol/**'
|
|
71
79
|
```
|
|
72
80
|
|
|
81
|
+
Choose exactly one selector family: repeated `--resource`, repeated `--claim`,
|
|
82
|
+
or `--all`. Duplicate selectors after normalization are rejected. A missing or
|
|
83
|
+
foreign explicit target fails without releasing any claims. Repeating an explicit release after its targets were removed is an error.
|
|
84
|
+
Batch release accepts no force-release authority.
|
|
85
|
+
|
|
86
|
+
To release all your claims while keeping your session and intent open:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
{{ACC}} release --all
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This selects only your session's current claims in its workspace. An empty set
|
|
93
|
+
succeeds. Each retry selects the current set and can release newly acquired claims.
|
|
94
|
+
Use `work --clear` to clear intent, `ack` to acknowledge a message, and `finish`
|
|
95
|
+
to record a handoff and close your ACC presence.
|
|
96
|
+
|
|
73
97
|
## Communicate only when it changes another agent's work
|
|
74
98
|
|
|
75
99
|
Send a message for a dependency, conflict, direct question, decision, or
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { preflightKimiIndicator } from "./indicator-install.mjs";
|
|
2
|
+
import { kimiIndicator } from "./indicator.mjs";
|
|
1
3
|
import { defineAdapter, projectContext, projectContextResult }
|
|
2
4
|
from "@agents-can-communicate/adapter-sdk";
|
|
3
5
|
import certification from "../certification.json" with { type: "json" };
|
|
@@ -28,6 +30,7 @@ export const KIMI_CODE_VERSION = "0.36.1";
|
|
|
28
30
|
export function createKimiAdapter() {
|
|
29
31
|
return defineAdapter({
|
|
30
32
|
id: "kimi",
|
|
33
|
+
statusIndicator: kimiIndicator,
|
|
31
34
|
displayName: "Kimi Code",
|
|
32
35
|
// The binary this client actually installs. Probed for a version to
|
|
33
36
|
// decide whether the client is on this machine, so it has to be the
|
|
@@ -57,6 +60,7 @@ export function createKimiAdapter() {
|
|
|
57
60
|
planInstall: context => planKimiInstall(forClient(context)),
|
|
58
61
|
detect: context => detectKimi(forClient(context)),
|
|
59
62
|
install: context => installKimiPlugin(forClient(context)),
|
|
63
|
+
preflightUninstall: context => preflightKimiIndicator(forClient(context)),
|
|
60
64
|
uninstall: context => uninstallKimiPlugin(forClient(context)),
|
|
61
65
|
|
|
62
66
|
doctor: async context => {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {configureTomlIndicator,indicatorCommand,compareVersionOrder,versionOrder} from '@agents-can-communicate/adapter-sdk';
|
|
4
|
+
import {kimiIndicator} from './indicator.mjs';
|
|
5
|
+
|
|
6
|
+
export const kimiIndicatorPaths = context => ({
|
|
7
|
+
file:path.join(context.home,'tui.toml'),
|
|
8
|
+
marker:path.join(context.dataHome??context.home,'acc','adapter-kimi','indicator.json'),
|
|
9
|
+
shim:path.join(context.home,'acc','indicator.mjs'),
|
|
10
|
+
});
|
|
11
|
+
export async function configureKimiIndicator(context,enabled) {
|
|
12
|
+
const paths=kimiIndicatorPaths(context);
|
|
13
|
+
|
|
14
|
+
if(enabled&&!supportsKimiIndicator(context))return [];
|
|
15
|
+
return configureTomlIndicator({...paths,table:['status_line'],enabled,set:async()=>({command:enabled?await indicatorCommand({context,adapterId:'kimi',...paths,shell:'cmd'}):''})});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const preflightKimiIndicator = context => configureTomlIndicator({...kimiIndicatorPaths(context),table:['status_line'],enabled:false,dryRun:true});
|
|
19
|
+
|
|
20
|
+
function supportsKimiIndicator(context) {
|
|
21
|
+
return typeof context.clientVersion === "string"
|
|
22
|
+
&& compareVersionOrder(versionOrder(context.clientVersion), versionOrder(kimiIndicator.minimumVersion)) >= 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function planKimiIndicator(context) {
|
|
26
|
+
const paths = kimiIndicatorPaths(context);
|
|
27
|
+
if (context.indicator === "on" ? !supportsKimiIndicator(context) : !existsSync(paths.marker)) return [];
|
|
28
|
+
return [{path: paths.file, kind: "merge"},
|
|
29
|
+
...(context.indicator === "on" && context.hostPlatform === "win32" ? [{path: paths.shim, kind: "tree"}] : [])];
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {indicatorGlyph,footerText,footerCwd} from '@agents-can-communicate/adapter-sdk';
|
|
2
|
+
|
|
3
|
+
export const kimiIndicator = {
|
|
4
|
+
minimumVersion:'2.1.1', reload:'run /reload-tui', budgetMs:180, previousTimeoutMs:80,
|
|
5
|
+
nativeSessionId: payload=>payload.sessionId,
|
|
6
|
+
renderText:indicatorGlyph,
|
|
7
|
+
composeText({payload={},indicator,previous='',settings={}}) {
|
|
8
|
+
const mode=[payload.permissionMode==='auto'?'auto':payload.permissionMode==='yolo'?'yolo':'',
|
|
9
|
+
payload.planMode===true?'plan':''].filter(Boolean).join(' · ');
|
|
10
|
+
const slots={mode,model:footerText(payload.model),cwd:footerCwd(payload.cwd),git:footerText(payload.gitBranch)};
|
|
11
|
+
const items=Array.isArray(settings.items)?settings.items:['mode','model','cwd','git'];
|
|
12
|
+
const base=previous.trimEnd().split(/\r?\n/)[0] || items.map(id=>slots[id]).filter(Boolean).join(' · ');
|
|
13
|
+
// The client has no session until the first prompt. This is not an ACC failure.
|
|
14
|
+
if(payload.sessionId==='')return base;
|
|
15
|
+
return [indicator,base].filter(Boolean).join(' │ ');
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {configureKimiIndicator, planKimiIndicator} from "./indicator-install.mjs";
|
|
1
2
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
@@ -125,8 +126,8 @@ const registerPlugin = (registry, root) => {
|
|
|
125
126
|
plugins: [...plugins, { id: PLUGIN_NAME, root, source: "local", enabled: true }] };
|
|
126
127
|
};
|
|
127
128
|
|
|
128
|
-
export async function installKimiPlugin(
|
|
129
|
-
hostPlatform = process.platform
|
|
129
|
+
export async function installKimiPlugin(context) {
|
|
130
|
+
const {home, runner = defaultRunner(), node, cli, hostPlatform = process.platform} = context;
|
|
130
131
|
// A hook whose command does not exist fails silently, on every event, for as
|
|
131
132
|
// long as it stays installed: the client reports nothing and ACC simply never
|
|
132
133
|
// sees a session. Writing that entry and hoping is worse than refusing.
|
|
@@ -135,6 +136,7 @@ export async function installKimiPlugin({ home, runner = defaultRunner(), node,
|
|
|
135
136
|
// plugin tree is laid down that nothing will then be able to remove.
|
|
136
137
|
const registered = await readJson(registryPath(home), { version: 1, plugins: [] });
|
|
137
138
|
|
|
139
|
+
const indicatorChanges = await configureKimiIndicator(context, context.indicator === "on");
|
|
138
140
|
const target = pluginPath(home);
|
|
139
141
|
await rm(target, { recursive: true, force: true });
|
|
140
142
|
await cp(bundle, target, { recursive: true });
|
|
@@ -161,13 +163,15 @@ export async function installKimiPlugin({ home, runner = defaultRunner(), node,
|
|
|
161
163
|
await writeForeignJson(registry, registerPlugin(registered, target),
|
|
162
164
|
{ readFile, writeFile, mkdir });
|
|
163
165
|
|
|
164
|
-
return { ok: true, changes: [target, file, registry], diagnostics:
|
|
166
|
+
return { ok: true, changes: [target, file, registry, ...indicatorChanges], diagnostics: context.indicator === "on"
|
|
167
|
+
? ["Kimi custom status line retains model, mode, cwd and branch; native context stays below. Goal and task badges are unavailable in the status payload."] : [] };
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
export async function uninstallKimiPlugin(
|
|
170
|
+
export async function uninstallKimiPlugin(context) {
|
|
171
|
+
const {home, keep = []} = context;
|
|
168
172
|
const file = configPath(home);
|
|
169
173
|
const existing = await readText(file, null);
|
|
170
|
-
const changes =
|
|
174
|
+
const changes = await configureKimiIndicator(context, false);
|
|
171
175
|
if (existing !== null) {
|
|
172
176
|
const stripped = stripBlock(existing);
|
|
173
177
|
if (stripped !== existing) changes.push(file);
|
|
@@ -218,8 +222,10 @@ export async function detectKimi({ home, runner = defaultRunner() }) {
|
|
|
218
222
|
* The paths an install would write, without writing them. Same helpers as the
|
|
219
223
|
* install, so a dry run cannot drift from what actually happens.
|
|
220
224
|
*/
|
|
221
|
-
export function planKimiInstall(
|
|
225
|
+
export function planKimiInstall(context) {
|
|
226
|
+
const {home} = context;
|
|
222
227
|
return [
|
|
228
|
+
...planKimiIndicator(context),
|
|
223
229
|
{ path: pluginPath(home), kind: "tree" },
|
|
224
230
|
{ path: configPath(home), kind: "merge" },
|
|
225
231
|
{ path: registryPath(home), kind: "merge" },
|
|
@@ -58,6 +58,7 @@ const ownBinary = name => {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export const defaultRunner = () => ownBinary("acc-hook.mjs");
|
|
61
|
+
export const defaultIndicator = () => ownBinary("acc-indicator.mjs");
|
|
61
62
|
export const defaultAntigravityRelay = () => ownBinary("acc-antigravity-relay.mjs");
|
|
62
63
|
|
|
63
64
|
export const runnerExists = async runner => {
|
|
@@ -11,7 +11,7 @@ export { NATIVE_ACTIVATION_KINDS, NATIVE_BINDING_MODES, NATIVE_REASON_CODES,
|
|
|
11
11
|
from "./native-delivery.mjs";
|
|
12
12
|
export { EVENT_KINDS, NORMALIZED_EVENT_KEYS, normalizedEvent } from "./events.mjs";
|
|
13
13
|
export { assertRunner, bakeSkillCommand, defaultAntigravityRelay, defaultCli, isShellWord,
|
|
14
|
-
defaultRunner, removeInstalledTree, runnerExists, shellQuote, writeCliShim, writeHookShim, writeNodeShim }
|
|
14
|
+
defaultRunner, defaultIndicator, removeInstalledTree, runnerExists, shellQuote, writeCliShim, writeHookShim, writeNodeShim }
|
|
15
15
|
from "./hook-shim.mjs";
|
|
16
16
|
export { BEGIN, END, removeTomlBlock, renderBlock, stripBlock, tomlString, writeTomlBlock }
|
|
17
17
|
from "./toml-block.mjs";
|
|
@@ -28,6 +28,7 @@ export { clearSessionBinding, listSessionBindings, loadSessionBinding,
|
|
|
28
28
|
from "./session-binding.mjs";
|
|
29
29
|
|
|
30
30
|
export { clearNativeAttempt, loadNativeAttempt, storeNativeAttempt } from "./native-attempt.mjs";
|
|
31
|
+
export { OFFER_ERRORS, loadOfferObservation, storeOfferObservation } from "./offer-observation.mjs";
|
|
31
32
|
export { channelSocketDirectory } from "./channel-directory.mjs";
|
|
32
33
|
export { processIsZombie, readProcessArgs, readProcessTable, readWindowsProcess, splitWindowsCommandLine }
|
|
33
34
|
from "./process-table.mjs";
|
|
@@ -37,3 +38,6 @@ export { windowsHookCommand } from "./windows-command.mjs";
|
|
|
37
38
|
export { shortPath } from "./windows-command.mjs";
|
|
38
39
|
export { mergeEnv } from "./executables.mjs";
|
|
39
40
|
export { closedTo, isWindowsPlatform, openRegularNoFollow } from "./private-files.mjs";
|
|
41
|
+
export { configureTomlIndicator, readTomlIndicatorSettings } from './toml-indicator.mjs';
|
|
42
|
+
export { indicatorGlyph, footerText, footerCwd } from './indicator-text.mjs';
|
|
43
|
+
export { indicatorCommand } from './indicator-command.mjs';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {mkdir,writeFile} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {defaultIndicator,shellQuote} from './hook-shim.mjs';
|
|
4
|
+
import {windowsHookCommand} from './windows-command.mjs';
|
|
5
|
+
|
|
6
|
+
export async function indicatorCommand({context,adapterId,marker,shim,shell}) {
|
|
7
|
+
const node=context.node??process.execPath,reader=context.indicatorRunner??defaultIndicator();
|
|
8
|
+
const args=['--adapter',adapterId,'--previous',marker];
|
|
9
|
+
if((context.hostPlatform??process.platform)!=='win32')return [node,reader,...args].map(shellQuote).join(' ');
|
|
10
|
+
// Bake paths as JS data, so no shell expands the marker path or its arguments.
|
|
11
|
+
await mkdir(path.dirname(shim),{recursive:true});
|
|
12
|
+
await writeFile(shim,`import {pathToFileURL} from 'node:url';\nprocess.argv = [process.execPath,${JSON.stringify(reader)},...${JSON.stringify(args)}];\nawait import(pathToFileURL(${JSON.stringify(reader)}).href);\n`);
|
|
13
|
+
return windowsHookCommand(shell,{node,shim,args:[]}).trimEnd();
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {stripVTControlCharacters} from 'node:util';
|
|
2
|
+
|
|
3
|
+
/** Status payload strings are data, never terminal control sequences. */
|
|
4
|
+
export const footerText = value => typeof value==='string'
|
|
5
|
+
? stripVTControlCharacters(value).replace(/[\p{Cc}\p{Cf}]/gu,' ').slice(0,160).trim() : '';
|
|
6
|
+
export function footerCwd(value) {
|
|
7
|
+
const text=footerText(value), parts=text.split(/[\\/]/).filter(Boolean);
|
|
8
|
+
return parts.length>3?`…/${parts.slice(-3).join('/')}`:text;
|
|
9
|
+
}
|
|
10
|
+
export function indicatorGlyph(report) {
|
|
11
|
+
const rgb=report.health==='ready'?'44;122;57':report.health==='problem'?'150;108;30':'102;102;102';
|
|
12
|
+
return report.label.replace(/^ACC ([●!…])/u,(_,glyph)=>`ACC \x1b[22;1;38;2;${rgb}m${glyph}\x1b[22;39m`);
|
|
13
|
+
}
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { parentPort, workerData } from "node:worker_threads";
|
|
5
5
|
|
|
6
6
|
import { assertPortableId } from "@agents-can-communicate/protocol";
|
|
7
|
-
import { withWriterMutex } from "@agents-can-communicate/storage-filesystem";
|
|
7
|
+
import { withWriterMutex } from "@agents-can-communicate/storage-filesystem/writer-mutex";
|
|
8
8
|
import { loadNativeAttempt, nativeAttemptFrom } from "./native-attempt.mjs";
|
|
9
9
|
|
|
10
10
|
const keyFor = harnessSessionId => createHash("sha256").update(String(harnessSessionId))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
let last = 0n;
|
|
2
|
+
|
|
3
|
+
// A tie-break for equal wall-clock milliseconds, captured in the caller before
|
|
4
|
+
// worker scheduling. Node's hrtime uses the OS monotonic clock; increment locally
|
|
5
|
+
// if its resolution produces a repeated tick. Wall time still orders other dates.
|
|
6
|
+
export function captureOfferOrder() {
|
|
7
|
+
const tick = process.hrtime.bigint();
|
|
8
|
+
last = tick > last ? tick : last + 1n;
|
|
9
|
+
return last.toString();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const validOfferOrder = value => typeof value === "string" && /^(0|[1-9]\d{0,19})$/.test(value);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parentPort, workerData as input } from "node:worker_threads";
|
|
5
|
+
import { readSessionRecord, withWriterMutex } from "@agents-can-communicate/storage-filesystem";
|
|
6
|
+
import { loadOfferObservation, offerObservationPath, OFFER_ERRORS } from "./offer-observation.mjs";
|
|
7
|
+
import { validOfferOrder } from "./offer-observation-order.mjs";
|
|
8
|
+
|
|
9
|
+
async function write() {
|
|
10
|
+
const { runtimeDir: root, workspaceId, sessionId, generation, at, order, state, reasonCode, deadlineAt } = input;
|
|
11
|
+
const file = offerObservationPath(root, sessionId);
|
|
12
|
+
if (!validOfferOrder(order) || !Number.isFinite(Date.parse(at)) || !["active", "degraded"].includes(state)
|
|
13
|
+
|| !(reasonCode === null || OFFER_ERRORS.includes(reasonCode))) return false;
|
|
14
|
+
const check = () => { if (Date.now() >= deadlineAt) throw new Error("observation deadline"); };
|
|
15
|
+
check();
|
|
16
|
+
return withWriterMutex({ locks: path.join(root, "offer-observation-locks", sessionId) },
|
|
17
|
+
{ root, deadlineAt, clock: { now: () => new Date().toISOString() } }, async () => {
|
|
18
|
+
check();
|
|
19
|
+
const session = await readSessionRecord({ root, workspaceId, sessionId });
|
|
20
|
+
if (session?.state !== "open" || session.generation !== generation) return false;
|
|
21
|
+
const previous = await loadOfferObservation(input);
|
|
22
|
+
if (previous && (Date.parse(previous.at) > Date.parse(at)
|
|
23
|
+
|| (Date.parse(previous.at) === Date.parse(at) && previous.order !== undefined
|
|
24
|
+
&& BigInt(previous.order) >= BigInt(order)))) return false;
|
|
25
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
26
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
27
|
+
try {
|
|
28
|
+
check();
|
|
29
|
+
await writeFile(temporary, JSON.stringify({ schemaVersion: 1, sessionId, generation,
|
|
30
|
+
at, order, state, reasonCode }) + "\n", { mode: 0o600 });
|
|
31
|
+
check();
|
|
32
|
+
await rename(temporary, file);
|
|
33
|
+
return true;
|
|
34
|
+
} finally { await rm(temporary, { force: true }).catch(() => {}); }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
parentPort.postMessage(await write().catch(() => false));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Worker } from "node:worker_threads";
|
|
4
|
+
import { assertPortableId } from "@agents-can-communicate/protocol";
|
|
5
|
+
import { captureOfferOrder, validOfferOrder } from "./offer-observation-order.mjs";
|
|
6
|
+
|
|
7
|
+
export const OFFER_ERRORS = Object.freeze(["ambiguous_recipient_sessions", "delivery_disabled",
|
|
8
|
+
"recipient_busy", "recipient_unavailable", "transport_error", "transport_rejected",
|
|
9
|
+
"transport_permission_denied", "unsupported_client_version", "inbound_approval_required"]);
|
|
10
|
+
|
|
11
|
+
export const offerObservationPath = (runtimeDir, sessionId) => {
|
|
12
|
+
assertPortableId(sessionId, "offer observation session id");
|
|
13
|
+
return path.join(runtimeDir, "offer-observations", `${sessionId}.json`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function loadOfferObservation({ runtimeDir, sessionId, generation }) {
|
|
17
|
+
try {
|
|
18
|
+
const record = JSON.parse(await readFile(offerObservationPath(runtimeDir, sessionId), "utf8"));
|
|
19
|
+
if (record.schemaVersion !== 1 || record.sessionId !== sessionId || record.generation !== generation
|
|
20
|
+
|| !Number.isFinite(Date.parse(record.at)) || !["active", "degraded"].includes(record.state)
|
|
21
|
+
|| !(record.reasonCode === null || OFFER_ERRORS.includes(record.reasonCode))
|
|
22
|
+
|| (record.order !== undefined && !validOfferOrder(record.order))) return null;
|
|
23
|
+
return { at: record.at, state: record.state, reasonCode: record.reasonCode,
|
|
24
|
+
...(record.order === undefined ? {} : { order: record.order }) };
|
|
25
|
+
} catch { return null; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Optional UI diagnostic, separate from delivery receipts and history. Failure
|
|
29
|
+
* to publish it cannot turn a recorded message into a failed send. */
|
|
30
|
+
export async function storeOfferObservation(input) {
|
|
31
|
+
if (typeof input.runtimeDir !== "string" || typeof input.workspaceId !== "string") return false;
|
|
32
|
+
let worker, timer;
|
|
33
|
+
try {
|
|
34
|
+
worker = new Worker(new URL("./offer-observation-writer.mjs", import.meta.url), {
|
|
35
|
+
workerData: { ...input, order: captureOfferOrder(), deadlineAt: Date.now() + 1500 },
|
|
36
|
+
});
|
|
37
|
+
const done = new Promise(resolve => {
|
|
38
|
+
worker.once("message", value => resolve(value === true));
|
|
39
|
+
worker.once("error", () => resolve(false));
|
|
40
|
+
worker.once("exit", () => resolve(false));
|
|
41
|
+
timer = setTimeout(resolve, 1500, false);
|
|
42
|
+
});
|
|
43
|
+
worker.unref();
|
|
44
|
+
return await done;
|
|
45
|
+
} catch { return false; }
|
|
46
|
+
finally { clearTimeout(timer); }
|
|
47
|
+
}
|
|
@@ -12,33 +12,45 @@ const DEFAULT_TIMEOUT_MS = 1_000;
|
|
|
12
12
|
const LINE = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/;
|
|
13
13
|
// A pid, then the whole rest of the line: a command line has spaces anywhere.
|
|
14
14
|
const ARGS_LINE = /^\s*(\d+)\s+(.+?)\s*$/;
|
|
15
|
-
//
|
|
16
|
-
//
|
|
15
|
+
// One command line can reach ARG_MAX, 1 MiB on macOS and execFile's default
|
|
16
|
+
// buffer; Windows reads several in one answer.
|
|
17
17
|
const ARGS_BUFFER = 16 * 1024 * 1024;
|
|
18
18
|
|
|
19
19
|
// Deep enough for a client that wraps its hook in a shell and a launcher.
|
|
20
20
|
const WINDOWS_HOPS = 8;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
22
|
+
// Deep enough for a client that wraps its hook in a shell and a launcher.
|
|
23
|
+
const POSIX_HOPS = 8;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The chain from `from` upward, one `ps -p` per process. Reading every
|
|
27
|
+
* process's command line took 830-1430 ms at load average 69 on a Mac with
|
|
28
|
+
* 1,900 processes (2026-10-12), past the hook's limit; one process took 7 ms.
|
|
29
|
+
* The limit bounds the whole walk. `until` ends it at the process the caller
|
|
30
|
+
* is looking for, so nothing above the client is read.
|
|
31
|
+
*/
|
|
32
|
+
async function posixChain({ from, run, timeoutMs, until }) {
|
|
33
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
34
|
const table = new Map();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
let next = Number(from);
|
|
36
|
+
for (let hop = 0; hop < POSIX_HOPS && next > 1 && !table.has(next); hop += 1) {
|
|
37
|
+
const timeout = deadline - Date.now();
|
|
38
|
+
if (timeout <= 0) break;
|
|
39
|
+
const options = { timeout, killSignal: "SIGKILL", windowsHide: true };
|
|
40
|
+
const pid = String(next);
|
|
41
|
+
const [tree, commandLine] = await Promise.all([
|
|
42
|
+
run("ps", ["-o", "pid=,ppid=,comm=", "-p", pid], options).then(({ stdout }) => String(stdout), () => ""),
|
|
43
|
+
run("ps", ["-o", "pid=,args=", "-p", pid], { ...options, maxBuffer: ARGS_BUFFER })
|
|
44
|
+
.then(({ stdout }) => String(stdout), () => ""),
|
|
45
|
+
]);
|
|
46
|
+
const match = LINE.exec(tree.trim());
|
|
47
|
+
if (match === null || Number(match[1]) !== next) break;
|
|
48
|
+
const args = ARGS_LINE.exec(commandLine.trim());
|
|
49
|
+
const entry = { ppid: Number(match[2]), comm: match[3],
|
|
50
|
+
...(args !== null && Number(args[1]) === next ? { args: args[2] } : {}) };
|
|
51
|
+
table.set(next, entry);
|
|
52
|
+
if (typeof until === "function" && until(entry)) break;
|
|
53
|
+
next = entry.ppid;
|
|
42
54
|
}
|
|
43
55
|
return table;
|
|
44
56
|
}
|
|
@@ -154,15 +166,16 @@ async function windowsTable({ from, run, timeoutMs, env }) {
|
|
|
154
166
|
* Processes on this machine, as pid -> parent, executable and, when it could be
|
|
155
167
|
* read, command line.
|
|
156
168
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
169
|
+
* Both platforms read the chain from `from` upward - the only part a hook asks
|
|
170
|
+
* about. POSIX asks `ps` one process at a time and stops early where `until`
|
|
171
|
+
* says; Windows reads up to eight hops in one query, with a creation time for
|
|
172
|
+
* each. A failed read is an empty map, never an error: an empty table resolves
|
|
173
|
+
* no client, which is the "nobody knows" answer every caller already handles.
|
|
161
174
|
*/
|
|
162
175
|
export async function readProcessTable({ platform = process.platform, from = process.pid,
|
|
163
|
-
run = execFileAsync, timeoutMs = DEFAULT_TIMEOUT_MS, env = process.env } = {}) {
|
|
176
|
+
run = execFileAsync, timeoutMs = DEFAULT_TIMEOUT_MS, env = process.env, until } = {}) {
|
|
164
177
|
if (platform === "win32") return windowsTable({ from, run, timeoutMs, env });
|
|
165
|
-
return
|
|
178
|
+
return posixChain({ from, run, timeoutMs, until });
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
/**
|