talking-stick 0.1.0-alpha
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 +166 -0
- package/dist/cli.js +701 -0
- package/dist/commands.js +70 -0
- package/dist/config.js +31 -0
- package/dist/db.js +177 -0
- package/dist/errors.js +20 -0
- package/dist/identity.js +184 -0
- package/dist/index.js +12 -0
- package/dist/install.js +272 -0
- package/dist/mcp-server.js +171 -0
- package/dist/path-resolution.js +101 -0
- package/dist/process-utils.js +93 -0
- package/dist/server.js +3 -0
- package/dist/service.js +980 -0
- package/dist/session-store.js +80 -0
- package/dist/skill-install.js +107 -0
- package/dist/types.js +1 -0
- package/docs/ambient-presence.md +191 -0
- package/docs/releases/0.1.0-alpha.md +32 -0
- package/docs/talking-stick-plan.md +1156 -0
- package/package.json +40 -0
- package/skills/talking-stick/SKILL.md +132 -0
- package/skills/talking-stick/agents/openai.yaml +4 -0
package/dist/commands.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { TalkingStickService } from "./service.js";
|
|
2
|
+
export class TalkingStickCommands {
|
|
3
|
+
service;
|
|
4
|
+
constructor(service = new TalkingStickService()) {
|
|
5
|
+
this.service = service;
|
|
6
|
+
}
|
|
7
|
+
close() {
|
|
8
|
+
this.service.close();
|
|
9
|
+
}
|
|
10
|
+
listRooms(input = {}) {
|
|
11
|
+
return this.service.listRooms(input);
|
|
12
|
+
}
|
|
13
|
+
joinPath(identity, input) {
|
|
14
|
+
return this.service.joinPath({
|
|
15
|
+
agent_id: identity.agent_id,
|
|
16
|
+
context_path: input.context_path,
|
|
17
|
+
force_new: input.force_new,
|
|
18
|
+
process_metadata: identity.process_metadata
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
waitForTurn(identity, input) {
|
|
22
|
+
return this.service.waitForTurn({
|
|
23
|
+
agent_id: identity.agent_id,
|
|
24
|
+
room_id: input.room_id,
|
|
25
|
+
cursor: input.cursor,
|
|
26
|
+
max_wait_ms: input.max_wait_ms
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
heartbeat(identity, input) {
|
|
30
|
+
return this.service.heartbeat({
|
|
31
|
+
agent_id: identity.agent_id,
|
|
32
|
+
room_id: input.room_id,
|
|
33
|
+
lease_id: input.lease_id,
|
|
34
|
+
expected_turn_id: input.expected_turn_id
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
releaseStick(identity, input) {
|
|
38
|
+
return this.service.releaseStick({
|
|
39
|
+
agent_id: identity.agent_id,
|
|
40
|
+
room_id: input.room_id,
|
|
41
|
+
lease_id: input.lease_id,
|
|
42
|
+
expected_turn_id: input.expected_turn_id,
|
|
43
|
+
handoff: input.handoff
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
passStick(identity, input) {
|
|
47
|
+
return this.service.passStick({
|
|
48
|
+
agent_id: identity.agent_id,
|
|
49
|
+
room_id: input.room_id,
|
|
50
|
+
lease_id: input.lease_id,
|
|
51
|
+
expected_turn_id: input.expected_turn_id,
|
|
52
|
+
to_agent_id: input.to_agent_id,
|
|
53
|
+
handoff: input.handoff
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
takeoverStick(identity, input) {
|
|
57
|
+
return this.service.takeoverStick({
|
|
58
|
+
agent_id: identity.agent_id,
|
|
59
|
+
room_id: input.room_id,
|
|
60
|
+
expected_turn_id: input.expected_turn_id,
|
|
61
|
+
reason: input.reason
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
getRoomState(input) {
|
|
65
|
+
return this.service.getRoomState(input);
|
|
66
|
+
}
|
|
67
|
+
getRoomEvents(input) {
|
|
68
|
+
return this.service.getRoomEvents(input);
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const defaultPolicy = {
|
|
4
|
+
ownerLeaseTtlMs: 45 * 60 * 1000,
|
|
5
|
+
heartbeatIntervalMs: 5 * 60 * 1000,
|
|
6
|
+
claimTtlMs: 20 * 60 * 1000,
|
|
7
|
+
waitForTurnMaxWaitMs: 30 * 1000,
|
|
8
|
+
waitForTurnPollMs: 250,
|
|
9
|
+
presenceTtlMs: 4 * 60 * 60 * 1000
|
|
10
|
+
};
|
|
11
|
+
export function resolveDataDir(options = {}) {
|
|
12
|
+
const env = options.env ?? process.env;
|
|
13
|
+
const platform = options.platform ?? process.platform;
|
|
14
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
15
|
+
const pathModule = platform === "win32" ? path.win32 : path.posix;
|
|
16
|
+
if (env.TALKING_STICK_DATA_DIR && env.TALKING_STICK_DATA_DIR.trim()) {
|
|
17
|
+
return path.resolve(env.TALKING_STICK_DATA_DIR);
|
|
18
|
+
}
|
|
19
|
+
if (platform === "win32") {
|
|
20
|
+
const appData = env.APPDATA;
|
|
21
|
+
if (!appData) {
|
|
22
|
+
return pathModule.join(homeDir, "AppData", "Roaming", "talking-stick");
|
|
23
|
+
}
|
|
24
|
+
return pathModule.join(appData, "talking-stick");
|
|
25
|
+
}
|
|
26
|
+
const xdgDataHome = env.XDG_DATA_HOME?.trim();
|
|
27
|
+
if (xdgDataHome) {
|
|
28
|
+
return pathModule.join(xdgDataHome, "talking-stick");
|
|
29
|
+
}
|
|
30
|
+
return pathModule.join(homeDir, ".local", "share", "talking-stick");
|
|
31
|
+
}
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import DatabaseConstructor from "better-sqlite3";
|
|
5
|
+
import { resolveDataDir } from "./config.js";
|
|
6
|
+
const migrations = [
|
|
7
|
+
{
|
|
8
|
+
id: 1,
|
|
9
|
+
name: "initial_schema",
|
|
10
|
+
up: `
|
|
11
|
+
CREATE TABLE path_rooms (
|
|
12
|
+
room_id TEXT PRIMARY KEY,
|
|
13
|
+
canonical_path TEXT NOT NULL,
|
|
14
|
+
sequence_index INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
owner TEXT,
|
|
16
|
+
reserved_for TEXT,
|
|
17
|
+
pending_handoff_event_seq INTEGER,
|
|
18
|
+
turn_id INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
lease_id TEXT,
|
|
20
|
+
lease_expires_at TEXT,
|
|
21
|
+
claim_expires_at TEXT,
|
|
22
|
+
state TEXT NOT NULL,
|
|
23
|
+
updated_at TEXT NOT NULL,
|
|
24
|
+
UNIQUE (canonical_path)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX path_rooms_canonical_path_idx
|
|
28
|
+
ON path_rooms (canonical_path);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE room_members (
|
|
31
|
+
room_id TEXT NOT NULL,
|
|
32
|
+
agent_id TEXT NOT NULL,
|
|
33
|
+
ordinal INTEGER NOT NULL,
|
|
34
|
+
joined_at TEXT NOT NULL,
|
|
35
|
+
last_seen_at TEXT NOT NULL,
|
|
36
|
+
status TEXT NOT NULL,
|
|
37
|
+
PRIMARY KEY (room_id, agent_id),
|
|
38
|
+
FOREIGN KEY (room_id) REFERENCES path_rooms(room_id)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE room_events (
|
|
42
|
+
event_seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
event_id TEXT NOT NULL UNIQUE,
|
|
44
|
+
room_id TEXT NOT NULL,
|
|
45
|
+
turn_id INTEGER NOT NULL,
|
|
46
|
+
event_type TEXT NOT NULL,
|
|
47
|
+
from_agent_id TEXT,
|
|
48
|
+
to_agent_id TEXT,
|
|
49
|
+
handoff_json TEXT,
|
|
50
|
+
reason TEXT,
|
|
51
|
+
created_at TEXT NOT NULL,
|
|
52
|
+
FOREIGN KEY (room_id) REFERENCES path_rooms(room_id)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE INDEX room_events_room_seq_idx
|
|
56
|
+
ON room_events (room_id, event_seq);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX room_events_room_turn_idx
|
|
59
|
+
ON room_events (room_id, turn_id);
|
|
60
|
+
`
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 2,
|
|
64
|
+
name: "room_member_process_metadata",
|
|
65
|
+
up: `
|
|
66
|
+
ALTER TABLE room_members ADD COLUMN host_id TEXT;
|
|
67
|
+
ALTER TABLE room_members ADD COLUMN pid INTEGER;
|
|
68
|
+
ALTER TABLE room_members ADD COLUMN process_started_at TEXT;
|
|
69
|
+
ALTER TABLE room_members ADD COLUMN session_kind TEXT NOT NULL DEFAULT 'mcp_harness';
|
|
70
|
+
ALTER TABLE room_members ADD COLUMN display_name TEXT;
|
|
71
|
+
`
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
export function resolveDatabasePath(options = {}) {
|
|
75
|
+
if (options.dbPath) {
|
|
76
|
+
return path.resolve(options.dbPath);
|
|
77
|
+
}
|
|
78
|
+
const dataDir = options.dataDir
|
|
79
|
+
? path.resolve(options.dataDir)
|
|
80
|
+
: resolveDataDir();
|
|
81
|
+
return path.join(dataDir, "rooms.sqlite");
|
|
82
|
+
}
|
|
83
|
+
export function openDatabase(options = {}) {
|
|
84
|
+
const dbPath = resolveDatabasePath(options);
|
|
85
|
+
assertLocalFilesystem(path.dirname(dbPath), options.filesystemTypeOptions);
|
|
86
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
87
|
+
const db = new DatabaseConstructor(dbPath);
|
|
88
|
+
applyPragmas(db);
|
|
89
|
+
migrate(db);
|
|
90
|
+
return db;
|
|
91
|
+
}
|
|
92
|
+
export function applyPragmas(db) {
|
|
93
|
+
db.pragma("journal_mode = WAL");
|
|
94
|
+
db.pragma("synchronous = NORMAL");
|
|
95
|
+
db.pragma("busy_timeout = 5000");
|
|
96
|
+
db.pragma("foreign_keys = ON");
|
|
97
|
+
}
|
|
98
|
+
export function migrate(db) {
|
|
99
|
+
db.exec(`
|
|
100
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
101
|
+
id INTEGER PRIMARY KEY,
|
|
102
|
+
name TEXT NOT NULL,
|
|
103
|
+
applied_at TEXT NOT NULL
|
|
104
|
+
);
|
|
105
|
+
`);
|
|
106
|
+
const applied = new Set(db
|
|
107
|
+
.prepare("SELECT id FROM schema_migrations")
|
|
108
|
+
.all()
|
|
109
|
+
.map((row) => row.id));
|
|
110
|
+
for (const migration of migrations) {
|
|
111
|
+
if (applied.has(migration.id)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
withImmediateTransaction(db, () => {
|
|
115
|
+
db.exec(migration.up);
|
|
116
|
+
db.prepare("INSERT INTO schema_migrations (id, name, applied_at) VALUES (?, ?, ?)").run(migration.id, migration.name, new Date().toISOString());
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function withImmediateTransaction(db, fn) {
|
|
121
|
+
db.prepare("BEGIN IMMEDIATE").run();
|
|
122
|
+
try {
|
|
123
|
+
const result = fn();
|
|
124
|
+
db.prepare("COMMIT").run();
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
db.prepare("ROLLBACK").run();
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function assertLocalFilesystem(targetPath, options = {}) {
|
|
133
|
+
const filesystemType = detectFilesystemType(targetPath, options);
|
|
134
|
+
if (!filesystemType) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (isRemoteFilesystemType(filesystemType)) {
|
|
138
|
+
throw new Error(`Talking Stick requires a local filesystem for SQLite state. Detected ${filesystemType} at ${targetPath}. Set TALKING_STICK_DATA_DIR to a local path.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function detectFilesystemType(targetPath, options = {}) {
|
|
142
|
+
const platform = options.platform ?? process.platform;
|
|
143
|
+
if (platform === "win32") {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const existingPath = nearestExistingPath(targetPath);
|
|
147
|
+
const execFile = options.execFile ??
|
|
148
|
+
((file, args, execOptions) => execFileSync(file, args, execOptions));
|
|
149
|
+
try {
|
|
150
|
+
const args = platform === "linux"
|
|
151
|
+
? ["-f", "-c", "%T", existingPath]
|
|
152
|
+
: ["-f", "%T", existingPath];
|
|
153
|
+
return execFile("stat", args, {
|
|
154
|
+
encoding: "utf8",
|
|
155
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
156
|
+
})
|
|
157
|
+
.trim()
|
|
158
|
+
.toLowerCase();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function nearestExistingPath(targetPath) {
|
|
165
|
+
let current = path.resolve(targetPath);
|
|
166
|
+
while (!fs.existsSync(current)) {
|
|
167
|
+
const parent = path.dirname(current);
|
|
168
|
+
if (parent === current) {
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
current = parent;
|
|
172
|
+
}
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
175
|
+
function isRemoteFilesystemType(filesystemType) {
|
|
176
|
+
return /^(nfs|smbfs|cifs)/.test(filesystemType);
|
|
177
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class ProtocolError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
details;
|
|
4
|
+
constructor(code, message, details = {}) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ProtocolError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.details = details;
|
|
9
|
+
}
|
|
10
|
+
toJSON() {
|
|
11
|
+
return {
|
|
12
|
+
error: this.code,
|
|
13
|
+
message: this.message,
|
|
14
|
+
...this.details
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function isProtocolError(error) {
|
|
19
|
+
return error instanceof ProtocolError;
|
|
20
|
+
}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createSystemProcessInspector } from "./process-utils.js";
|
|
5
|
+
export function deriveHumanCliIdentity(options = {}) {
|
|
6
|
+
const username = options.username ?? safeUsername();
|
|
7
|
+
const displayName = options.displayName ?? username;
|
|
8
|
+
const agentId = options.agentId ?? `human:${sanitizeIdentityComponent(username)}`;
|
|
9
|
+
const sessionKind = options.sessionKind ?? "human_cli";
|
|
10
|
+
const includesExactProcessIdentity = sessionKind === "human_guardian";
|
|
11
|
+
let hostId = null;
|
|
12
|
+
let pid = null;
|
|
13
|
+
let processStartedAt = null;
|
|
14
|
+
if (includesExactProcessIdentity) {
|
|
15
|
+
const inspector = options.inspector ?? createSystemProcessInspector();
|
|
16
|
+
pid = options.pid ?? process.pid;
|
|
17
|
+
const inspection = inspector.inspect(pid);
|
|
18
|
+
hostId = options.hostId ?? os.hostname();
|
|
19
|
+
processStartedAt = inspection?.startTime ?? null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
agent_id: agentId,
|
|
23
|
+
process_metadata: {
|
|
24
|
+
host_id: hostId,
|
|
25
|
+
pid,
|
|
26
|
+
process_started_at: processStartedAt,
|
|
27
|
+
session_kind: sessionKind,
|
|
28
|
+
display_name: displayName
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function deriveMcpHarnessIdentity(options = {}) {
|
|
33
|
+
const env = options.env ?? process.env;
|
|
34
|
+
const inspector = options.inspector ?? createSystemProcessInspector();
|
|
35
|
+
const pid = options.parentPid ?? process.ppid;
|
|
36
|
+
const hostId = options.hostId ?? os.hostname();
|
|
37
|
+
const username = options.username ?? safeUsername();
|
|
38
|
+
const inspection = inspector.inspect(pid);
|
|
39
|
+
const signal = detectHarnessSignal(env);
|
|
40
|
+
if (signal) {
|
|
41
|
+
const sessionId = resolveHarnessSessionId(signal, env, pid, inspection, username, hostId);
|
|
42
|
+
const agentId = options.agentId ?? harnessAgentId(signal.harness, sessionId, hostId, username);
|
|
43
|
+
return {
|
|
44
|
+
agent_id: agentId,
|
|
45
|
+
process_metadata: {
|
|
46
|
+
host_id: hostId,
|
|
47
|
+
pid,
|
|
48
|
+
process_started_at: inspection?.startTime ?? null,
|
|
49
|
+
session_kind: "mcp_harness",
|
|
50
|
+
display_name: signal.harness
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const displayName = options.displayName ?? deriveCommandLabel(inspection?.command ?? null);
|
|
55
|
+
const agentId = options.agentId ??
|
|
56
|
+
`${sanitizeIdentityComponent(displayName)}:${hashIdentityParts([
|
|
57
|
+
hostId,
|
|
58
|
+
String(pid),
|
|
59
|
+
inspection?.startTime ?? "",
|
|
60
|
+
options.sessionId ?? ""
|
|
61
|
+
])}`;
|
|
62
|
+
return {
|
|
63
|
+
agent_id: agentId,
|
|
64
|
+
process_metadata: {
|
|
65
|
+
host_id: hostId,
|
|
66
|
+
pid,
|
|
67
|
+
process_started_at: inspection?.startTime ?? null,
|
|
68
|
+
session_kind: "mcp_harness",
|
|
69
|
+
display_name: displayName
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function deriveHarnessCliIdentity(options = {}) {
|
|
74
|
+
const env = options.env ?? process.env;
|
|
75
|
+
const signal = detectHarnessSignal(env);
|
|
76
|
+
if (!signal)
|
|
77
|
+
return null;
|
|
78
|
+
const parentPid = options.parentPid ?? process.ppid;
|
|
79
|
+
const hostId = options.hostId ?? os.hostname();
|
|
80
|
+
const inspector = options.inspector ?? createSystemProcessInspector();
|
|
81
|
+
const parentInspection = inspector.inspect(parentPid);
|
|
82
|
+
const username = options.username ?? safeUsername();
|
|
83
|
+
const sessionId = resolveHarnessSessionId(signal, env, parentPid, parentInspection, username, hostId);
|
|
84
|
+
const agentId = options.agentId ?? harnessAgentId(signal.harness, sessionId, hostId, username);
|
|
85
|
+
return {
|
|
86
|
+
agent_id: agentId,
|
|
87
|
+
process_metadata: {
|
|
88
|
+
host_id: hostId,
|
|
89
|
+
pid: parentPid,
|
|
90
|
+
process_started_at: parentInspection?.startTime ?? null,
|
|
91
|
+
session_kind: "harness_cli",
|
|
92
|
+
display_name: signal.harness
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function harnessAgentId(harness, sessionId, hostId, username) {
|
|
97
|
+
return `${harness}:${hashIdentityParts([
|
|
98
|
+
harness,
|
|
99
|
+
hostId,
|
|
100
|
+
sessionId,
|
|
101
|
+
sanitizeIdentityComponent(username)
|
|
102
|
+
])}`;
|
|
103
|
+
}
|
|
104
|
+
function resolveHarnessSessionId(signal, env, parentPid, parentInspection, username, hostId) {
|
|
105
|
+
if (signal.sessionId)
|
|
106
|
+
return `harness:${signal.sessionId}`;
|
|
107
|
+
const terminalId = resolveTerminalSessionId(env);
|
|
108
|
+
if (terminalId)
|
|
109
|
+
return terminalId;
|
|
110
|
+
if (parentInspection?.startTime) {
|
|
111
|
+
return `pid:${parentPid}@${parentInspection.startTime}`;
|
|
112
|
+
}
|
|
113
|
+
return `userhost:${sanitizeIdentityComponent(username)}@${hostId}`;
|
|
114
|
+
}
|
|
115
|
+
const TERMINAL_SESSION_ENV_VARS = [
|
|
116
|
+
"ITERM_SESSION_ID",
|
|
117
|
+
"CMUX_TAB_ID",
|
|
118
|
+
"KITTY_WINDOW_ID",
|
|
119
|
+
"WEZTERM_PANE",
|
|
120
|
+
"TERMINATOR_UUID",
|
|
121
|
+
"TMUX_PANE",
|
|
122
|
+
"STY"
|
|
123
|
+
];
|
|
124
|
+
function resolveTerminalSessionId(env) {
|
|
125
|
+
for (const key of TERMINAL_SESSION_ENV_VARS) {
|
|
126
|
+
const value = env[key];
|
|
127
|
+
if (value && value.trim().length > 0) {
|
|
128
|
+
return `term:${key}=${value.trim()}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function detectHarnessSignal(env) {
|
|
134
|
+
if (env.CLAUDECODE === "1") {
|
|
135
|
+
return { harness: "claude", sessionId: nonEmpty(env.CLAUDE_CODE_EXECPATH) };
|
|
136
|
+
}
|
|
137
|
+
if (env.CODEX_MANAGED_BY_NPM === "1" || nonEmpty(env.CODEX_THREAD_ID)) {
|
|
138
|
+
return { harness: "codex", sessionId: nonEmpty(env.CODEX_THREAD_ID) };
|
|
139
|
+
}
|
|
140
|
+
if (env.GEMINI_CLI === "1") {
|
|
141
|
+
return { harness: "gemini", sessionId: null };
|
|
142
|
+
}
|
|
143
|
+
if (env.OPENCODE === "1") {
|
|
144
|
+
return {
|
|
145
|
+
harness: "opencode",
|
|
146
|
+
sessionId: nonEmpty(env.OPENCODE_RUN_ID) ?? nonEmpty(env.OPENCODE_PID)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function nonEmpty(value) {
|
|
152
|
+
return value && value.trim().length > 0 ? value : null;
|
|
153
|
+
}
|
|
154
|
+
function deriveCommandLabel(command) {
|
|
155
|
+
if (!command) {
|
|
156
|
+
return "harness";
|
|
157
|
+
}
|
|
158
|
+
const token = command.trim().split(/\s+/)[0] ?? "";
|
|
159
|
+
const basename = path.basename(token).replace(/\.(exe|cmd|bat)$/i, "");
|
|
160
|
+
const sanitized = sanitizeIdentityComponent(basename);
|
|
161
|
+
return sanitized || "harness";
|
|
162
|
+
}
|
|
163
|
+
function safeUsername() {
|
|
164
|
+
try {
|
|
165
|
+
return os.userInfo().username;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return process.env.USER ?? process.env.LOGNAME ?? "user";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function sanitizeIdentityComponent(value) {
|
|
172
|
+
const sanitized = value
|
|
173
|
+
.toLowerCase()
|
|
174
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
175
|
+
.replace(/^-+|-+$/g, "");
|
|
176
|
+
return sanitized || "anon";
|
|
177
|
+
}
|
|
178
|
+
function hashIdentityParts(parts) {
|
|
179
|
+
return crypto
|
|
180
|
+
.createHash("sha256")
|
|
181
|
+
.update(parts.join("\0"))
|
|
182
|
+
.digest("hex")
|
|
183
|
+
.slice(0, 8);
|
|
184
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { resolveDataDir, defaultPolicy } from "./config.js";
|
|
2
|
+
export { TalkingStickCommands } from "./commands.js";
|
|
3
|
+
export { applyPragmas, assertLocalFilesystem, detectFilesystemType, migrate, openDatabase, resolveDatabasePath, withImmediateTransaction } from "./db.js";
|
|
4
|
+
export { ProtocolError, isProtocolError } from "./errors.js";
|
|
5
|
+
export { deriveHarnessCliIdentity, deriveHumanCliIdentity, deriveMcpHarnessIdentity } from "./identity.js";
|
|
6
|
+
export { ancestorPaths, canonicalizeContextPath, resolveContextPath, resolveWorkspaceRoot } from "./path-resolution.js";
|
|
7
|
+
export { createMcpServer, runStdioServer } from "./mcp-server.js";
|
|
8
|
+
export { SUPPORTED_HARNESSES, detectHarness, parseHarnessList, planInstall, planUninstall, resolveOpencodeConfigPath, runAction } from "./install.js";
|
|
9
|
+
export { DEFAULT_SKILL_NAME, planSkillInstall, planSkillUninstall, resolveBundledSkillPath, resolveSkillTargetPath } from "./skill-install.js";
|
|
10
|
+
export { createSystemProcessInspector, terminateKnownProcess } from "./process-utils.js";
|
|
11
|
+
export { clearCliSessionLease, findCliSessionByRoom, findCliSessionForContextPath, readCliSessions, resolveCliSessionPath, upsertCliSession, writeCliSessions } from "./session-store.js";
|
|
12
|
+
export { TalkingStickService } from "./service.js";
|