@agentconnect.md/daemon 1.0.0-rc.65 → 1.0.0-rc.67
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/index.js +299 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13227,7 +13227,9 @@ const SessionListItem = object({
|
|
|
13227
13227
|
status: string().optional(),
|
|
13228
13228
|
lastActivityAt: string().optional(),
|
|
13229
13229
|
usage: SessionUsage.optional(),
|
|
13230
|
-
triggeredBy: string().optional()
|
|
13230
|
+
triggeredBy: string().optional(),
|
|
13231
|
+
channelName: string().optional(),
|
|
13232
|
+
triggeredByName: string().optional()
|
|
13231
13233
|
});
|
|
13232
13234
|
/** C→D REQ: list the daemon's sessions (optionally just one agent's). */
|
|
13233
13235
|
const SessionListReq = object({ agentId: string().uuid().optional() });
|
|
@@ -13237,6 +13239,7 @@ const SessionListPage = object({ sessions: array(SessionListItem) });
|
|
|
13237
13239
|
const SessionMessage = object({
|
|
13238
13240
|
seq: number().int(),
|
|
13239
13241
|
sender: string(),
|
|
13242
|
+
senderName: string().optional(),
|
|
13240
13243
|
ts: string(),
|
|
13241
13244
|
kind: string(),
|
|
13242
13245
|
text: string()
|
|
@@ -13319,6 +13322,50 @@ const WorkspaceReadContent = object({
|
|
|
13319
13322
|
nextOffset: number().int().nonnegative().optional(),
|
|
13320
13323
|
truncated: boolean().optional()
|
|
13321
13324
|
});
|
|
13325
|
+
/**
|
|
13326
|
+
* Agent workspace git ops (C→D REQ → REP) — the console's on-demand controls for
|
|
13327
|
+
* a git-repo workspace. Like list/read these run daemon-local; the CP proxies the
|
|
13328
|
+
* outcome and stores nothing.
|
|
13329
|
+
*
|
|
13330
|
+
* These are DATA-oriented: a non-repo workspace (`isRepo:false`), a dirty tree,
|
|
13331
|
+
* or a pull that can't fast-forward (offline, diverged, local edits) all come
|
|
13332
|
+
* back as a normal REP — NOT an `error` frame. Only an unknown agent
|
|
13333
|
+
* (`BAD_PAYLOAD`) or an unexpected git/fs failure (`INTERNAL`) is an error.
|
|
13334
|
+
*/
|
|
13335
|
+
/** C→D REQ: report `git status` of the agent's workspace (is it clean?). */
|
|
13336
|
+
const WorkspaceGitStatusReq = object({ agentId: string().min(1) });
|
|
13337
|
+
/** One changed path in a `git status`. `index`/`workingDir` are git's per-file XY
|
|
13338
|
+
* status chars (' ', 'M', 'A', 'D', 'R', 'C', 'U', '?', …). */
|
|
13339
|
+
const WorkspaceGitFile = object({
|
|
13340
|
+
path: string(),
|
|
13341
|
+
index: string(),
|
|
13342
|
+
workingDir: string()
|
|
13343
|
+
});
|
|
13344
|
+
/** D→C REP (corr = the req id): the working-tree status. */
|
|
13345
|
+
const WorkspaceGitStatus = object({
|
|
13346
|
+
agentId: string(),
|
|
13347
|
+
isRepo: boolean(),
|
|
13348
|
+
clean: boolean(),
|
|
13349
|
+
branch: string().optional(),
|
|
13350
|
+
tracking: string().optional(),
|
|
13351
|
+
ahead: number().int().nonnegative().optional(),
|
|
13352
|
+
behind: number().int().nonnegative().optional(),
|
|
13353
|
+
files: array(WorkspaceGitFile).optional(),
|
|
13354
|
+
truncated: boolean().optional()
|
|
13355
|
+
});
|
|
13356
|
+
/** C→D REQ: force a fast-forward-only `git pull` in the agent's workspace now. */
|
|
13357
|
+
const WorkspaceGitPullReq = object({ agentId: string().min(1) });
|
|
13358
|
+
/** D→C REP (corr = the req id): the pull outcome. A failed pull is DATA
|
|
13359
|
+
* (`ok:false` + `detail`), not an error frame (see the section docblock). */
|
|
13360
|
+
const WorkspaceGitPullResult = object({
|
|
13361
|
+
agentId: string(),
|
|
13362
|
+
isRepo: boolean(),
|
|
13363
|
+
ok: boolean(),
|
|
13364
|
+
detail: string().optional(),
|
|
13365
|
+
changed: number().int().nonnegative().optional(),
|
|
13366
|
+
insertions: number().int().nonnegative().optional(),
|
|
13367
|
+
deletions: number().int().nonnegative().optional()
|
|
13368
|
+
});
|
|
13322
13369
|
//#endregion
|
|
13323
13370
|
//#region ../protocol/dist/frames/telemetry.js
|
|
13324
13371
|
/**
|
|
@@ -13443,6 +13490,10 @@ const FRAME_SCHEMAS = {
|
|
|
13443
13490
|
"workspace/list/page": WorkspaceListPage,
|
|
13444
13491
|
"workspace/read": WorkspaceReadReq,
|
|
13445
13492
|
"workspace/read/content": WorkspaceReadContent,
|
|
13493
|
+
"workspace/gitstatus": WorkspaceGitStatusReq,
|
|
13494
|
+
"workspace/gitstatus/result": WorkspaceGitStatus,
|
|
13495
|
+
"workspace/gitpull": WorkspaceGitPullReq,
|
|
13496
|
+
"workspace/gitpull/result": WorkspaceGitPullResult,
|
|
13446
13497
|
"config/push": object({ keys: record(string(), unknown()) }),
|
|
13447
13498
|
"daemon/restart": object({
|
|
13448
13499
|
reason: string(),
|
|
@@ -13542,6 +13593,10 @@ discriminatedUnion("type", [
|
|
|
13542
13593
|
frame("workspace/list/page", FRAME_SCHEMAS["workspace/list/page"]),
|
|
13543
13594
|
frame("workspace/read", FRAME_SCHEMAS["workspace/read"]),
|
|
13544
13595
|
frame("workspace/read/content", FRAME_SCHEMAS["workspace/read/content"]),
|
|
13596
|
+
frame("workspace/gitstatus", FRAME_SCHEMAS["workspace/gitstatus"]),
|
|
13597
|
+
frame("workspace/gitstatus/result", FRAME_SCHEMAS["workspace/gitstatus/result"]),
|
|
13598
|
+
frame("workspace/gitpull", FRAME_SCHEMAS["workspace/gitpull"]),
|
|
13599
|
+
frame("workspace/gitpull/result", FRAME_SCHEMAS["workspace/gitpull/result"]),
|
|
13545
13600
|
frame("config/push", FRAME_SCHEMAS["config/push"]),
|
|
13546
13601
|
frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
|
|
13547
13602
|
frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
|
|
@@ -13668,7 +13723,7 @@ function isFrame(type) {
|
|
|
13668
13723
|
}
|
|
13669
13724
|
//#endregion
|
|
13670
13725
|
//#region src/workspace/workspace-manager.ts
|
|
13671
|
-
const PULL_TIMEOUT_MS = 4500;
|
|
13726
|
+
const PULL_TIMEOUT_MS$1 = 4500;
|
|
13672
13727
|
const cloneInFlight = /* @__PURE__ */ new Map();
|
|
13673
13728
|
async function prepareWorkspace(agent) {
|
|
13674
13729
|
const cwd = agent.workspace.path;
|
|
@@ -13683,7 +13738,7 @@ async function prepareWorkspace(agent) {
|
|
|
13683
13738
|
return cwd;
|
|
13684
13739
|
}
|
|
13685
13740
|
if (agent.workspace.pullOnNewSession) try {
|
|
13686
|
-
await Promise.race([simpleGit(cwd).pull(["--ff-only"]), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("pull timeout")), PULL_TIMEOUT_MS))]);
|
|
13741
|
+
await Promise.race([simpleGit(cwd).pull(["--ff-only"]), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("pull timeout")), PULL_TIMEOUT_MS$1))]);
|
|
13687
13742
|
} catch {}
|
|
13688
13743
|
return cwd;
|
|
13689
13744
|
}
|
|
@@ -23761,7 +23816,12 @@ var LocalStore = class {
|
|
|
23761
23816
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
23762
23817
|
key TEXT PRIMARY KEY, agentId TEXT, platform TEXT, channel TEXT, thread TEXT,
|
|
23763
23818
|
acpSessionId TEXT, state TEXT, lastDeliveredTs TEXT, updatedAt INTEGER,
|
|
23764
|
-
usage TEXT, muted INTEGER
|
|
23819
|
+
usage TEXT, muted INTEGER, triggeredBy TEXT
|
|
23820
|
+
);
|
|
23821
|
+
-- Platform id → human display name (Slack channel/user names, daemon-resolved
|
|
23822
|
+
-- and cached here so session read-back can label ids without a Slack call).
|
|
23823
|
+
CREATE TABLE IF NOT EXISTS display_names (
|
|
23824
|
+
id TEXT PRIMARY KEY, name TEXT NOT NULL, updatedAt INTEGER
|
|
23765
23825
|
);
|
|
23766
23826
|
CREATE TABLE IF NOT EXISTS transcript (
|
|
23767
23827
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -23780,14 +23840,15 @@ var LocalStore = class {
|
|
|
23780
23840
|
`);
|
|
23781
23841
|
this.migrateSessionUsage();
|
|
23782
23842
|
}
|
|
23783
|
-
/** Add the `sessions.usage` (JSON token accounting)
|
|
23784
|
-
* thread mute) columns to a pre-existing DB. CREATE
|
|
23785
|
-
* them for fresh DBs but never alters an existing
|
|
23786
|
-
* No-op once the columns are present. */
|
|
23843
|
+
/** Add the `sessions.usage` (JSON token accounting), `sessions.muted` (`!stop`
|
|
23844
|
+
* thread mute) and `sessions.triggeredBy` columns to a pre-existing DB. CREATE
|
|
23845
|
+
* TABLE IF NOT EXISTS above adds them for fresh DBs but never alters an existing
|
|
23846
|
+
* table, so upgrade in place. No-op once the columns are present. */
|
|
23787
23847
|
migrateSessionUsage() {
|
|
23788
23848
|
const cols = this.db.prepare("PRAGMA table_info(sessions)").all();
|
|
23789
23849
|
if (!cols.some((c) => c.name === "usage")) this.db.exec("ALTER TABLE sessions ADD COLUMN usage TEXT");
|
|
23790
23850
|
if (!cols.some((c) => c.name === "muted")) this.db.exec("ALTER TABLE sessions ADD COLUMN muted INTEGER");
|
|
23851
|
+
if (!cols.some((c) => c.name === "triggeredBy")) this.db.exec("ALTER TABLE sessions ADD COLUMN triggeredBy TEXT");
|
|
23791
23852
|
}
|
|
23792
23853
|
/**
|
|
23793
23854
|
* Pre-`kind` transcript tables keyed on (channel, thread, ts) hold only conversational
|
|
@@ -23837,11 +23898,12 @@ var LocalStore = class {
|
|
|
23837
23898
|
};
|
|
23838
23899
|
}
|
|
23839
23900
|
upsertSession(rec) {
|
|
23840
|
-
this.db.prepare(`INSERT INTO sessions (key, agentId, platform, channel, thread, acpSessionId, state, lastDeliveredTs, updatedAt)
|
|
23841
|
-
VALUES (@key, @agentId, @platform, @channel, @thread, @acpSessionId, @state, @lastDeliveredTs, @updatedAt)
|
|
23901
|
+
this.db.prepare(`INSERT INTO sessions (key, agentId, platform, channel, thread, acpSessionId, state, lastDeliveredTs, updatedAt, triggeredBy)
|
|
23902
|
+
VALUES (@key, @agentId, @platform, @channel, @thread, @acpSessionId, @state, @lastDeliveredTs, @updatedAt, @triggeredBy)
|
|
23842
23903
|
ON CONFLICT(key) DO UPDATE SET
|
|
23843
23904
|
acpSessionId=excluded.acpSessionId, state=excluded.state,
|
|
23844
|
-
lastDeliveredTs=excluded.lastDeliveredTs, updatedAt=excluded.updatedAt
|
|
23905
|
+
lastDeliveredTs=excluded.lastDeliveredTs, updatedAt=excluded.updatedAt,
|
|
23906
|
+
triggeredBy=COALESCE(sessions.triggeredBy, excluded.triggeredBy)`).run({
|
|
23845
23907
|
key: rec.key,
|
|
23846
23908
|
agentId: rec.agentId,
|
|
23847
23909
|
platform: rec.platform,
|
|
@@ -23850,7 +23912,8 @@ var LocalStore = class {
|
|
|
23850
23912
|
acpSessionId: rec.acpSessionId,
|
|
23851
23913
|
state: rec.state,
|
|
23852
23914
|
lastDeliveredTs: rec.lastDeliveredTs,
|
|
23853
|
-
updatedAt: rec.updatedAt
|
|
23915
|
+
updatedAt: rec.updatedAt,
|
|
23916
|
+
triggeredBy: rec.triggeredBy ?? null
|
|
23854
23917
|
});
|
|
23855
23918
|
}
|
|
23856
23919
|
/** Targeted state transition for an existing session (§7.3), stamping `updatedAt`
|
|
@@ -23947,6 +24010,22 @@ var LocalStore = class {
|
|
|
23947
24010
|
activeSessionCountSince(channel, thread, sinceTs) {
|
|
23948
24011
|
return this.db.prepare("SELECT COUNT(*) AS n FROM sessions WHERE channel = ? AND thread = ? AND state != 'closed' AND updatedAt >= ?").get(channel, thread, sinceTs)?.n ?? 0;
|
|
23949
24012
|
}
|
|
24013
|
+
/** Cache a platform id's human display name (channel or user; Slack ids don't
|
|
24014
|
+
* collide across the two). Latest-wins — renames overwrite. */
|
|
24015
|
+
setDisplayName(id, name, updatedAt) {
|
|
24016
|
+
this.db.prepare(`INSERT INTO display_names (id, name, updatedAt) VALUES (?, ?, ?)
|
|
24017
|
+
ON CONFLICT(id) DO UPDATE SET name=excluded.name, updatedAt=excluded.updatedAt`).run(id, name, updatedAt);
|
|
24018
|
+
}
|
|
24019
|
+
/** Display names for a set of platform ids — only the ids that have one.
|
|
24020
|
+
* One batched `IN (…)` query, not a round-trip per id. */
|
|
24021
|
+
getDisplayNames(ids) {
|
|
24022
|
+
const out = /* @__PURE__ */ new Map();
|
|
24023
|
+
const unique = [...new Set(ids)];
|
|
24024
|
+
if (unique.length === 0) return out;
|
|
24025
|
+
const rows = this.db.prepare(`SELECT id, name FROM display_names WHERE id IN (${unique.map(() => "?").join(",")})`).all(...unique);
|
|
24026
|
+
for (const r of rows) if (r.name) out.set(r.id, r.name);
|
|
24027
|
+
return out;
|
|
24028
|
+
}
|
|
23950
24029
|
getCpRouting() {
|
|
23951
24030
|
return this.db.prepare("SELECT routingEpoch, assignments, globalRules FROM cp_routing WHERE id = 1").get();
|
|
23952
24031
|
}
|
|
@@ -24067,7 +24146,8 @@ var SessionManager = class {
|
|
|
24067
24146
|
acpSessionId,
|
|
24068
24147
|
state: "idle",
|
|
24069
24148
|
lastDeliveredTs: null,
|
|
24070
|
-
updatedAt: Date.now()
|
|
24149
|
+
updatedAt: Date.now(),
|
|
24150
|
+
triggeredBy: msg.sender.id
|
|
24071
24151
|
};
|
|
24072
24152
|
this.deps.store.upsertSession(rec);
|
|
24073
24153
|
} else if (host.hasSession?.(rec.acpSessionId) === false) {
|
|
@@ -80959,6 +81039,74 @@ var SlackConnection = class {
|
|
|
80959
81039
|
}
|
|
80960
81040
|
};
|
|
80961
81041
|
//#endregion
|
|
81042
|
+
//#region src/slack/name-resolver.ts
|
|
81043
|
+
/**
|
|
81044
|
+
* Resolves Slack ids (channel "C…", user "U…/W…") to human display names off the
|
|
81045
|
+
* inbound-message path and caches the results into the LocalStore `display_names`
|
|
81046
|
+
* table, so session read-back (`session/list` / `session/history`) can label ids
|
|
81047
|
+
* without ever calling Slack at read time.
|
|
81048
|
+
*
|
|
81049
|
+
* Design constraints:
|
|
81050
|
+
* - Fire-and-forget: never on the dispatch hot path, never throws into it.
|
|
81051
|
+
* - Rate-friendly: an in-memory attempt cache means each id hits the Web API at
|
|
81052
|
+
* most once per TTL (short TTL after a failure, long after success/no-name),
|
|
81053
|
+
* regardless of message volume.
|
|
81054
|
+
* - Names persist in SQLite, so a daemon restart keeps stale-but-useful names
|
|
81055
|
+
* while the cache warms back up (rename lag is bounded by OK_TTL_MS).
|
|
81056
|
+
*/
|
|
81057
|
+
const OK_TTL_MS = 360 * 60 * 1e3;
|
|
81058
|
+
const FAIL_TTL_MS = 600 * 1e3;
|
|
81059
|
+
const MAX_TRACKED_IDS = 5e3;
|
|
81060
|
+
var SlackNameResolver = class {
|
|
81061
|
+
save;
|
|
81062
|
+
log;
|
|
81063
|
+
now;
|
|
81064
|
+
/** id → epoch ms until which we won't re-attempt a lookup. */
|
|
81065
|
+
nextAttemptAt = /* @__PURE__ */ new Map();
|
|
81066
|
+
constructor(save, log, now = Date.now) {
|
|
81067
|
+
this.save = save;
|
|
81068
|
+
this.log = log;
|
|
81069
|
+
this.now = now;
|
|
81070
|
+
}
|
|
81071
|
+
/** Kick off (cached) resolution for a message's channel + human sender. */
|
|
81072
|
+
noteMessage(conn, msg) {
|
|
81073
|
+
this.resolveChannel(conn, msg.channel);
|
|
81074
|
+
if (!msg.sender.isBot && /^[UW]/.test(msg.sender.id)) this.resolveUser(conn, msg.sender.id);
|
|
81075
|
+
}
|
|
81076
|
+
claim(id) {
|
|
81077
|
+
const at = this.nextAttemptAt.get(id);
|
|
81078
|
+
if (at !== void 0 && this.now() < at) return false;
|
|
81079
|
+
this.nextAttemptAt.delete(id);
|
|
81080
|
+
this.nextAttemptAt.set(id, this.now() + OK_TTL_MS);
|
|
81081
|
+
for (const key of this.nextAttemptAt.keys()) {
|
|
81082
|
+
if (this.nextAttemptAt.size <= MAX_TRACKED_IDS) break;
|
|
81083
|
+
this.nextAttemptAt.delete(key);
|
|
81084
|
+
}
|
|
81085
|
+
return true;
|
|
81086
|
+
}
|
|
81087
|
+
async resolveChannel(conn, channel) {
|
|
81088
|
+
if (!this.claim(channel)) return;
|
|
81089
|
+
try {
|
|
81090
|
+
const info = await conn.getChannelInfo(channel);
|
|
81091
|
+
if (info.name) this.save(channel, info.name);
|
|
81092
|
+
} catch (err) {
|
|
81093
|
+
this.nextAttemptAt.set(channel, this.now() + FAIL_TTL_MS);
|
|
81094
|
+
this.log?.debug(`slack: name lookup failed for channel ${channel}: ${err.message}`);
|
|
81095
|
+
}
|
|
81096
|
+
}
|
|
81097
|
+
async resolveUser(conn, user) {
|
|
81098
|
+
if (!this.claim(user)) return;
|
|
81099
|
+
try {
|
|
81100
|
+
const p = await conn.getUserProfile(user);
|
|
81101
|
+
const name = p.realName || p.name;
|
|
81102
|
+
if (name) this.save(user, name);
|
|
81103
|
+
} catch (err) {
|
|
81104
|
+
this.nextAttemptAt.set(user, this.now() + FAIL_TTL_MS);
|
|
81105
|
+
this.log?.debug(`slack: name lookup failed for user ${user}: ${err.message}`);
|
|
81106
|
+
}
|
|
81107
|
+
}
|
|
81108
|
+
};
|
|
81109
|
+
//#endregion
|
|
80962
81110
|
//#region src/slack/formatter.ts
|
|
80963
81111
|
/**
|
|
80964
81112
|
* Last-mile Slack formatting (§9.1 / §9.3). Two pure helpers:
|
|
@@ -82667,6 +82815,12 @@ var CpClient = class {
|
|
|
82667
82815
|
case "workspace/read":
|
|
82668
82816
|
this.deps.workspaceRead.read(frame.payload).then((content) => this.reply(frame, "workspace/read/content", content)).catch((err) => this.workspaceError(frame.id, "workspace/read", err));
|
|
82669
82817
|
return;
|
|
82818
|
+
case "workspace/gitstatus":
|
|
82819
|
+
this.deps.workspaceGit.status(frame.payload.agentId).then((status) => this.reply(frame, "workspace/gitstatus/result", status)).catch((err) => this.workspaceError(frame.id, "workspace/gitstatus", err));
|
|
82820
|
+
return;
|
|
82821
|
+
case "workspace/gitpull":
|
|
82822
|
+
this.deps.workspaceGit.pull(frame.payload.agentId).then((result) => this.reply(frame, "workspace/gitpull/result", result)).catch((err) => this.workspaceError(frame.id, "workspace/gitpull", err));
|
|
82823
|
+
return;
|
|
82670
82824
|
case "agent/prompt":
|
|
82671
82825
|
this.sendError(frame.id, "INTERNAL", "agent/prompt not implemented", false);
|
|
82672
82826
|
return;
|
|
@@ -82711,8 +82865,12 @@ function parseUsage(raw) {
|
|
|
82711
82865
|
function createSessionReader(store) {
|
|
82712
82866
|
return {
|
|
82713
82867
|
list(req) {
|
|
82714
|
-
|
|
82868
|
+
const rows = store.listSessions(req.agentId);
|
|
82869
|
+
const names = store.getDisplayNames(rows.flatMap((r) => [r.channel, ...r.triggeredBy ? [r.triggeredBy] : []]));
|
|
82870
|
+
return { sessions: rows.map((r) => {
|
|
82715
82871
|
const usage = parseUsage(r.usage);
|
|
82872
|
+
const channelName = names.get(r.channel);
|
|
82873
|
+
const triggeredByName = r.triggeredBy ? names.get(r.triggeredBy) : void 0;
|
|
82716
82874
|
return {
|
|
82717
82875
|
sessionId: r.acpSessionId,
|
|
82718
82876
|
sessionKey: {
|
|
@@ -82723,7 +82881,10 @@ function createSessionReader(store) {
|
|
|
82723
82881
|
agentId: r.agentId,
|
|
82724
82882
|
status: r.state,
|
|
82725
82883
|
lastActivityAt: new Date(r.updatedAt).toISOString(),
|
|
82726
|
-
...usage ? { usage } : {}
|
|
82884
|
+
...usage ? { usage } : {},
|
|
82885
|
+
...r.triggeredBy ? { triggeredBy: r.triggeredBy } : {},
|
|
82886
|
+
...channelName ? { channelName } : {},
|
|
82887
|
+
...triggeredByName ? { triggeredByName } : {}
|
|
82727
82888
|
};
|
|
82728
82889
|
}) };
|
|
82729
82890
|
},
|
|
@@ -82735,13 +82896,19 @@ function createSessionReader(store) {
|
|
|
82735
82896
|
};
|
|
82736
82897
|
const before = req.cursor != null && Number.isFinite(Number(req.cursor)) ? Number(req.cursor) : null;
|
|
82737
82898
|
const { rows, hasMore } = store.transcriptPage(rec.channel, rec.thread, before, req.limit);
|
|
82738
|
-
const
|
|
82739
|
-
|
|
82740
|
-
|
|
82741
|
-
|
|
82742
|
-
|
|
82743
|
-
|
|
82744
|
-
|
|
82899
|
+
const ordered = rows.slice().reverse();
|
|
82900
|
+
const names = store.getDisplayNames(ordered.map((r) => r.sender));
|
|
82901
|
+
const messages = ordered.map((r) => {
|
|
82902
|
+
const senderName = names.get(r.sender);
|
|
82903
|
+
return {
|
|
82904
|
+
seq: r.seq,
|
|
82905
|
+
sender: r.sender,
|
|
82906
|
+
...senderName ? { senderName } : {},
|
|
82907
|
+
ts: r.ts,
|
|
82908
|
+
kind: r.kind,
|
|
82909
|
+
text: r.text
|
|
82910
|
+
};
|
|
82911
|
+
});
|
|
82745
82912
|
return {
|
|
82746
82913
|
sessionId: req.sessionId,
|
|
82747
82914
|
messages,
|
|
@@ -82751,6 +82918,104 @@ function createSessionReader(store) {
|
|
|
82751
82918
|
};
|
|
82752
82919
|
}
|
|
82753
82920
|
//#endregion
|
|
82921
|
+
//#region src/cp/workspace-git.ts
|
|
82922
|
+
/**
|
|
82923
|
+
* `WorkspaceGit` — the seam answering the CP's `workspace/gitstatus` and
|
|
82924
|
+
* `workspace/gitpull` REQs against an agent's local git-repo workspace. Sibling
|
|
82925
|
+
* to {@link WorkspaceReader}: git runs daemon-local and only the outcome is
|
|
82926
|
+
* proxied to the console (§1/§12), never the repo.
|
|
82927
|
+
*
|
|
82928
|
+
* DATA vs error: a from-scratch workspace (`isRepo:false`), a dirty tree, or a
|
|
82929
|
+
* pull that can't fast-forward (offline, diverged, would clobber local edits) are
|
|
82930
|
+
* all normal REPs — the caller renders them. Only an unknown agentId throws
|
|
82931
|
+
* ({@link WorkspaceViolationError} → `BAD_PAYLOAD`); an unexpected git/fs failure
|
|
82932
|
+
* on `status` propagates (→ `INTERNAL`). Git-provided text is scrubbed of the
|
|
82933
|
+
* absolute workspace path before it leaves the daemon (raw git errors embed host
|
|
82934
|
+
* paths that must not reach the CP/UI).
|
|
82935
|
+
*/
|
|
82936
|
+
/** On-demand pull is interactive, so allow more headroom than the 4.5s
|
|
82937
|
+
* best-effort pull at session start (workspace-manager.ts). */
|
|
82938
|
+
const PULL_TIMEOUT_MS = 2e4;
|
|
82939
|
+
/** Cap the changed-file list so a huge working tree can't overflow the frame. */
|
|
82940
|
+
const MAX_STATUS_FILES = 500;
|
|
82941
|
+
function createWorkspaceGit(workspaceRootByAgent) {
|
|
82942
|
+
function rootFor(agentId) {
|
|
82943
|
+
const root = workspaceRootByAgent(agentId);
|
|
82944
|
+
if (!root) throw new WorkspaceViolationError(`unknown agent "${agentId}"`);
|
|
82945
|
+
return root;
|
|
82946
|
+
}
|
|
82947
|
+
/** A git-repo workspace has a `.git` at its root; from-scratch does not. */
|
|
82948
|
+
function isRepo(root) {
|
|
82949
|
+
return existsSync(join(root, ".git"));
|
|
82950
|
+
}
|
|
82951
|
+
/** Strip the absolute workspace path out of git-provided text so no host path
|
|
82952
|
+
* leaks to the CP/UI. Repo-relative paths in the message are kept. */
|
|
82953
|
+
function scrub(root, msg) {
|
|
82954
|
+
return msg.split(root).join("<workspace>").trim();
|
|
82955
|
+
}
|
|
82956
|
+
return {
|
|
82957
|
+
async status(agentId) {
|
|
82958
|
+
const root = rootFor(agentId);
|
|
82959
|
+
if (!isRepo(root)) return {
|
|
82960
|
+
agentId,
|
|
82961
|
+
isRepo: false,
|
|
82962
|
+
clean: true
|
|
82963
|
+
};
|
|
82964
|
+
const s = await simpleGit(root).status();
|
|
82965
|
+
const files = s.files.slice(0, MAX_STATUS_FILES).map((f) => ({
|
|
82966
|
+
path: f.path,
|
|
82967
|
+
index: f.index,
|
|
82968
|
+
workingDir: f.working_dir
|
|
82969
|
+
}));
|
|
82970
|
+
return {
|
|
82971
|
+
agentId,
|
|
82972
|
+
isRepo: true,
|
|
82973
|
+
clean: s.isClean(),
|
|
82974
|
+
...s.current ? { branch: s.current } : {},
|
|
82975
|
+
...s.tracking ? { tracking: s.tracking } : {},
|
|
82976
|
+
ahead: s.ahead,
|
|
82977
|
+
behind: s.behind,
|
|
82978
|
+
...files.length ? { files } : {},
|
|
82979
|
+
...s.files.length > files.length ? { truncated: true } : {}
|
|
82980
|
+
};
|
|
82981
|
+
},
|
|
82982
|
+
async pull(agentId) {
|
|
82983
|
+
const root = rootFor(agentId);
|
|
82984
|
+
if (!isRepo(root)) return {
|
|
82985
|
+
agentId,
|
|
82986
|
+
isRepo: false,
|
|
82987
|
+
ok: false,
|
|
82988
|
+
detail: "workspace is not a git checkout"
|
|
82989
|
+
};
|
|
82990
|
+
let timer;
|
|
82991
|
+
try {
|
|
82992
|
+
const res = await Promise.race([simpleGit(root).pull(["--ff-only"]), new Promise((_, rej) => {
|
|
82993
|
+
timer = setTimeout(() => rej(/* @__PURE__ */ new Error("pull timed out")), PULL_TIMEOUT_MS);
|
|
82994
|
+
})]);
|
|
82995
|
+
const changed = res.files.length;
|
|
82996
|
+
return {
|
|
82997
|
+
agentId,
|
|
82998
|
+
isRepo: true,
|
|
82999
|
+
ok: true,
|
|
83000
|
+
changed,
|
|
83001
|
+
insertions: res.summary.insertions,
|
|
83002
|
+
deletions: res.summary.deletions,
|
|
83003
|
+
detail: changed > 0 ? `Fast-forwarded — updated ${changed} file${changed === 1 ? "" : "s"}.` : "Already up to date."
|
|
83004
|
+
};
|
|
83005
|
+
} catch (err) {
|
|
83006
|
+
return {
|
|
83007
|
+
agentId,
|
|
83008
|
+
isRepo: true,
|
|
83009
|
+
ok: false,
|
|
83010
|
+
detail: scrub(root, err?.message ?? "pull failed")
|
|
83011
|
+
};
|
|
83012
|
+
} finally {
|
|
83013
|
+
if (timer) clearTimeout(timer);
|
|
83014
|
+
}
|
|
83015
|
+
}
|
|
83016
|
+
};
|
|
83017
|
+
}
|
|
83018
|
+
//#endregion
|
|
82754
83019
|
//#region src/cp/cp-cron.ts
|
|
82755
83020
|
/**
|
|
82756
83021
|
* `CpCronRegistry` — the daemon's set of CP-pushed crons, keyed by `cronId`
|
|
@@ -83178,6 +83443,7 @@ var Daemon = class Daemon {
|
|
|
83178
83443
|
fileAgents = /* @__PURE__ */ new Map();
|
|
83179
83444
|
hosts = /* @__PURE__ */ new Map();
|
|
83180
83445
|
connections = [];
|
|
83446
|
+
nameResolver;
|
|
83181
83447
|
scheduler;
|
|
83182
83448
|
sessions;
|
|
83183
83449
|
connByIntegration = /* @__PURE__ */ new Map();
|
|
@@ -83247,6 +83513,7 @@ var Daemon = class Daemon {
|
|
|
83247
83513
|
const skipped = Object.keys(resolvedRuntimes).filter((id) => !this.runtimes[id]);
|
|
83248
83514
|
if (skipped.length) this.log.info(`runtimes not installed (skipped): ${skipped.join(", ")}`);
|
|
83249
83515
|
this.store = new LocalStore(statePath(root));
|
|
83516
|
+
this.nameResolver = new SlackNameResolver((id, name) => this.store.setDisplayName(id, name, Date.now()), this.log);
|
|
83250
83517
|
this.cpRouting = new CpRoutingLayer({
|
|
83251
83518
|
load: () => {
|
|
83252
83519
|
const row = this.store.getCpRouting();
|
|
@@ -83321,7 +83588,10 @@ var Daemon = class Daemon {
|
|
|
83321
83588
|
const conn = new SlackConnection({
|
|
83322
83589
|
group,
|
|
83323
83590
|
newTraceId: () => randomUUID(),
|
|
83324
|
-
onMessage: (msg) =>
|
|
83591
|
+
onMessage: (msg) => {
|
|
83592
|
+
this.nameResolver?.noteMessage(conn, msg);
|
|
83593
|
+
this.onInbound(msg);
|
|
83594
|
+
},
|
|
83325
83595
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
83326
83596
|
log: this.log,
|
|
83327
83597
|
boltDebug: cfg.logging.level === "debug" || cfg.logging.level === "trace"
|
|
@@ -83495,7 +83765,10 @@ var Daemon = class Daemon {
|
|
|
83495
83765
|
const conn = new SlackConnection({
|
|
83496
83766
|
group,
|
|
83497
83767
|
newTraceId: () => randomUUID(),
|
|
83498
|
-
onMessage: (msg) =>
|
|
83768
|
+
onMessage: (msg) => {
|
|
83769
|
+
this.nameResolver?.noteMessage(conn, msg);
|
|
83770
|
+
this.onInbound(msg);
|
|
83771
|
+
},
|
|
83499
83772
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
83500
83773
|
log: this.log,
|
|
83501
83774
|
boltDebug: this.cfg.logging.level === "debug" || this.cfg.logging.level === "trace"
|
|
@@ -83526,6 +83799,7 @@ var Daemon = class Daemon {
|
|
|
83526
83799
|
try {
|
|
83527
83800
|
const channels = await conn.listBotChannels();
|
|
83528
83801
|
if (!channels) return;
|
|
83802
|
+
for (const c of channels) if (c.name) this.store.setDisplayName(c.id, c.name, Date.now());
|
|
83529
83803
|
for (const [integrationId, c] of this.connByIntegration) {
|
|
83530
83804
|
if (c !== conn) continue;
|
|
83531
83805
|
this.channelSnapshots.set(integrationId, channels);
|
|
@@ -84250,6 +84524,7 @@ var Daemon = class Daemon {
|
|
|
84250
84524
|
configApply: this.cpConfigApply(),
|
|
84251
84525
|
sessionRead: createSessionReader(this.store),
|
|
84252
84526
|
workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
|
|
84527
|
+
workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
|
|
84253
84528
|
clock: systemClock,
|
|
84254
84529
|
connect: () => ClientTransport.dial(url),
|
|
84255
84530
|
log: this.log
|