@integrity-labs/agt-cli 0.27.163 → 0.27.164
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/bin/agt.js +3 -3
- package/dist/{chunk-DTZKJYJP.js → chunk-OIX32YSR.js} +1 -1
- package/dist/lib/manager-worker.js +2 -2
- package/dist/mcp/slack-channel.js +179 -1
- package/dist/mcp/telegram-channel.js +170 -1
- package/package.json +1 -1
- /package/dist/{chunk-DTZKJYJP.js.map → chunk-OIX32YSR.js.map} +0 -0
package/dist/bin/agt.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
success,
|
|
34
34
|
table,
|
|
35
35
|
warn
|
|
36
|
-
} from "../chunk-
|
|
36
|
+
} from "../chunk-OIX32YSR.js";
|
|
37
37
|
import {
|
|
38
38
|
CHANNEL_REGISTRY,
|
|
39
39
|
DEPLOYMENT_TEMPLATES,
|
|
@@ -5019,7 +5019,7 @@ import { execFileSync, execSync } from "child_process";
|
|
|
5019
5019
|
import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
|
|
5020
5020
|
import chalk18 from "chalk";
|
|
5021
5021
|
import ora16 from "ora";
|
|
5022
|
-
var cliVersion = true ? "0.27.
|
|
5022
|
+
var cliVersion = true ? "0.27.164" : "dev";
|
|
5023
5023
|
async function fetchLatestVersion() {
|
|
5024
5024
|
const host2 = getHost();
|
|
5025
5025
|
if (!host2) return null;
|
|
@@ -5942,7 +5942,7 @@ function handleError(err) {
|
|
|
5942
5942
|
}
|
|
5943
5943
|
|
|
5944
5944
|
// src/bin/agt.ts
|
|
5945
|
-
var cliVersion2 = true ? "0.27.
|
|
5945
|
+
var cliVersion2 = true ? "0.27.164" : "dev";
|
|
5946
5946
|
var program = new Command();
|
|
5947
5947
|
program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
|
|
5948
5948
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
provisionStopHook,
|
|
23
23
|
requireHost,
|
|
24
24
|
safeWriteJsonAtomic
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-OIX32YSR.js";
|
|
26
26
|
import {
|
|
27
27
|
getProjectDir as getProjectDir2,
|
|
28
28
|
getReadyTasks,
|
|
@@ -5012,7 +5012,7 @@ var cachedMaintenanceWindow = null;
|
|
|
5012
5012
|
var lastVersionCheckAt = 0;
|
|
5013
5013
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
5014
5014
|
var lastResponsivenessProbeAt = 0;
|
|
5015
|
-
var agtCliVersion = true ? "0.27.
|
|
5015
|
+
var agtCliVersion = true ? "0.27.164" : "dev";
|
|
5016
5016
|
function resolveBrewPath(execFileSync4) {
|
|
5017
5017
|
try {
|
|
5018
5018
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -14344,6 +14344,110 @@ function decideInboundAccess(input) {
|
|
|
14344
14344
|
return { kind: "admit" };
|
|
14345
14345
|
}
|
|
14346
14346
|
|
|
14347
|
+
// src/watch-command.ts
|
|
14348
|
+
var WATCH_DEFAULT_DURATION_MS = 2 * 60 * 60 * 1e3;
|
|
14349
|
+
var WATCH_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14350
|
+
var WATCH_MIN_DURATION_MS = 5 * 60 * 1e3;
|
|
14351
|
+
function extractDriveFileId(s) {
|
|
14352
|
+
const trimmed = s.trim().replace(/^<|>$/g, "");
|
|
14353
|
+
const pathId = trimmed.match(/\/d\/([A-Za-z0-9_-]{10,})/);
|
|
14354
|
+
if (pathId?.[1]) return pathId[1];
|
|
14355
|
+
const idParam = trimmed.match(/[?&]id=([A-Za-z0-9_-]{10,})/);
|
|
14356
|
+
if (idParam?.[1]) return idParam[1];
|
|
14357
|
+
if (!/[/:?]/.test(trimmed) && /^[A-Za-z0-9_-]{20,}$/.test(trimmed)) return trimmed;
|
|
14358
|
+
return null;
|
|
14359
|
+
}
|
|
14360
|
+
function parseDurationToken(token) {
|
|
14361
|
+
const m = token.trim().match(/^(\d+)\s*([smhd])?$/i);
|
|
14362
|
+
if (!m?.[1]) return null;
|
|
14363
|
+
const n = Number.parseInt(m[1], 10);
|
|
14364
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
14365
|
+
const unit = (m[2] ?? "m").toLowerCase();
|
|
14366
|
+
const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
14367
|
+
return n * mult;
|
|
14368
|
+
}
|
|
14369
|
+
function parseWatchArgs(args) {
|
|
14370
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
14371
|
+
const first = parts[0];
|
|
14372
|
+
if (!first) return { ok: false, error: "usage" };
|
|
14373
|
+
if (parts.length > 2) return { ok: false, error: "usage" };
|
|
14374
|
+
const fileId = extractDriveFileId(first);
|
|
14375
|
+
if (!fileId) return { ok: false, error: "bad-url" };
|
|
14376
|
+
let durationMs = WATCH_DEFAULT_DURATION_MS;
|
|
14377
|
+
const durationToken = parts[1];
|
|
14378
|
+
if (durationToken !== void 0) {
|
|
14379
|
+
const parsed = parseDurationToken(durationToken);
|
|
14380
|
+
if (parsed === null) return { ok: false, error: "bad-duration" };
|
|
14381
|
+
durationMs = Math.min(Math.max(parsed, WATCH_MIN_DURATION_MS), WATCH_MAX_DURATION_MS);
|
|
14382
|
+
}
|
|
14383
|
+
return { ok: true, value: { fileId, durationMs } };
|
|
14384
|
+
}
|
|
14385
|
+
function watchArgsFromText(strippedText, codeName, opts) {
|
|
14386
|
+
const forms = [];
|
|
14387
|
+
if (opts?.allowBare ?? true) forms.push("/watch");
|
|
14388
|
+
if (codeName) forms.push(`/watch-${codeName}`);
|
|
14389
|
+
for (const f of forms) {
|
|
14390
|
+
if (strippedText === f) return "";
|
|
14391
|
+
if (strippedText.startsWith(`${f} `)) return strippedText.slice(f.length + 1).trim();
|
|
14392
|
+
}
|
|
14393
|
+
return null;
|
|
14394
|
+
}
|
|
14395
|
+
async function postWatchTrigger(opts) {
|
|
14396
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
14397
|
+
try {
|
|
14398
|
+
const res = await doFetch(`${opts.host}/host/triggers`, {
|
|
14399
|
+
method: "POST",
|
|
14400
|
+
headers: {
|
|
14401
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
14402
|
+
Authorization: `Bearer ${opts.apiKey}`
|
|
14403
|
+
},
|
|
14404
|
+
body: JSON.stringify({
|
|
14405
|
+
agent_id: opts.agentId,
|
|
14406
|
+
provider: "gdrive_comments",
|
|
14407
|
+
config: { fileId: opts.fileId },
|
|
14408
|
+
expires_at: opts.expiresAtIso
|
|
14409
|
+
}),
|
|
14410
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
14411
|
+
});
|
|
14412
|
+
if (!res.ok) {
|
|
14413
|
+
let msg = `HTTP ${res.status}`;
|
|
14414
|
+
try {
|
|
14415
|
+
const j2 = await res.json();
|
|
14416
|
+
if (j2?.error) msg = j2.error;
|
|
14417
|
+
} catch {
|
|
14418
|
+
}
|
|
14419
|
+
return { ok: false, status: res.status, error: msg };
|
|
14420
|
+
}
|
|
14421
|
+
const j = await res.json().catch(() => ({}));
|
|
14422
|
+
return { ok: true, status: res.status, reused: !!j.reused, expiresAt: j.expires_at ?? null };
|
|
14423
|
+
} catch (err) {
|
|
14424
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
14425
|
+
}
|
|
14426
|
+
}
|
|
14427
|
+
function humanDuration(ms) {
|
|
14428
|
+
const mins = Math.round(ms / 6e4);
|
|
14429
|
+
if (mins % 1440 === 0) return `${mins / 1440}d`;
|
|
14430
|
+
if (mins % 60 === 0) return `${mins / 60}h`;
|
|
14431
|
+
return `${mins}m`;
|
|
14432
|
+
}
|
|
14433
|
+
function watchUsageText() {
|
|
14434
|
+
return "Usage: `/watch <google-doc-url> [duration]` \u2014 e.g. `/watch https://docs.google.com/document/d/\u2026/edit 2h`. Duration defaults to 2h (max 7d).";
|
|
14435
|
+
}
|
|
14436
|
+
function watchBadUrlText() {
|
|
14437
|
+
return "That doesn't look like a Google Doc link. Paste the doc's share URL (the part with `/d/<id>`).";
|
|
14438
|
+
}
|
|
14439
|
+
function watchBadDurationText() {
|
|
14440
|
+
return "I couldn't read that duration. Try `30m`, `2h`, or `1d` (max 7d).";
|
|
14441
|
+
}
|
|
14442
|
+
function watchErrorText() {
|
|
14443
|
+
return "\u274C I couldn't set up that watch just now. Please try again in a moment.";
|
|
14444
|
+
}
|
|
14445
|
+
function watchSuccessTextSlack(fileId, durationMs, reused) {
|
|
14446
|
+
const url = `https://docs.google.com/document/d/${fileId}/edit`;
|
|
14447
|
+
const verb = reused ? "Extended my watch on" : "Watching";
|
|
14448
|
+
return `\u{1F440} ${verb} <${url}|that doc> for new comments that mention me, for the next ${humanDuration(durationMs)}. I'll pause automatically when the window's up. (Make sure Google Drive is connected with comment access, or I won't see them.)`;
|
|
14449
|
+
}
|
|
14450
|
+
|
|
14347
14451
|
// src/ack-reaction.ts
|
|
14348
14452
|
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
14349
14453
|
import { join as join2 } from "path";
|
|
@@ -17214,6 +17318,7 @@ function buildSlackHelpMessage(codeName) {
|
|
|
17214
17318
|
`\u2022 \`${agentSlashCommand("/help")}\` (or type \`/help\`) \u2014 show this help`,
|
|
17215
17319
|
`\u2022 \`${agentSlashCommand("/restart")}\` \u2014 restart this agent`,
|
|
17216
17320
|
`\u2022 \`${agentSlashCommand("/status")}\` \u2014 this agent's model, session origin, uptime + connectivity`,
|
|
17321
|
+
"\u2022 `/watch <google-doc-url> [duration]` (type it in chat) \u2014 watch a Google Doc for comments that mention me (default 2h, max 7d; auto-pauses when the window ends). In a shared channel, address me as `/watch-<my-name>`.",
|
|
17217
17322
|
"\u2022 `/kill` \u2014 silence all agents in this thread for 6h (use as a thread reply)",
|
|
17218
17323
|
"\u2022 `/unkill` \u2014 clear a kill (use as a thread reply)",
|
|
17219
17324
|
`\u2022 \`${agentSlashCommand("/investigate")}\` \u2014 live tail of this agent's terminal pane (DM only, allowlisted users; works while the channel process is alive \u2014 a wedged host still needs SSM diagnostics)`
|
|
@@ -17727,6 +17832,61 @@ async function denyUnauthorizedRestart(opts) {
|
|
|
17727
17832
|
...opts.threadTs ? { thread_ts: opts.threadTs } : {}
|
|
17728
17833
|
});
|
|
17729
17834
|
}
|
|
17835
|
+
async function handleWatchCommand(opts) {
|
|
17836
|
+
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
17837
|
+
const replyThread = opts.threadTs ?? opts.ts;
|
|
17838
|
+
const post = (text) => postSlackMessage({
|
|
17839
|
+
channel: opts.channel,
|
|
17840
|
+
text,
|
|
17841
|
+
...replyThread ? { thread_ts: replyThread } : {}
|
|
17842
|
+
});
|
|
17843
|
+
const parsed = parseWatchArgs(watchArgsFromText(opts.rawText, AGENT_CODE_NAME) ?? "");
|
|
17844
|
+
if (!parsed.ok) {
|
|
17845
|
+
await post(
|
|
17846
|
+
parsed.error === "bad-url" ? watchBadUrlText() : parsed.error === "bad-duration" ? watchBadDurationText() : watchUsageText()
|
|
17847
|
+
);
|
|
17848
|
+
return;
|
|
17849
|
+
}
|
|
17850
|
+
if (!AGT_HOST || !AGT_API_KEY || !AGT_AGENT_ID) {
|
|
17851
|
+
process.stderr.write(`slack-channel(${codeName}): /watch missing AGT_* env \u2014 cannot create watch
|
|
17852
|
+
`);
|
|
17853
|
+
await post(watchErrorText());
|
|
17854
|
+
return;
|
|
17855
|
+
}
|
|
17856
|
+
const expiresAtIso = new Date(Date.now() + parsed.value.durationMs).toISOString();
|
|
17857
|
+
const res = await postWatchTrigger({
|
|
17858
|
+
host: AGT_HOST,
|
|
17859
|
+
apiKey: AGT_API_KEY,
|
|
17860
|
+
agentId: AGT_AGENT_ID,
|
|
17861
|
+
fileId: parsed.value.fileId,
|
|
17862
|
+
expiresAtIso
|
|
17863
|
+
});
|
|
17864
|
+
if (!res.ok) {
|
|
17865
|
+
process.stderr.write(
|
|
17866
|
+
`slack-channel(${codeName}): /watch create failed (status=${res.status ?? "?"}): ${res.error ?? "unknown"}
|
|
17867
|
+
`
|
|
17868
|
+
);
|
|
17869
|
+
await post(watchErrorText());
|
|
17870
|
+
return;
|
|
17871
|
+
}
|
|
17872
|
+
process.stderr.write(
|
|
17873
|
+
`slack-channel(${codeName}): /watch ${res.reused ? "extended" : "created"} for doc ${parsed.value.fileId.slice(0, 8)}\u2026 channel ${hashChannelId(opts.channel)}
|
|
17874
|
+
`
|
|
17875
|
+
);
|
|
17876
|
+
await post(watchSuccessTextSlack(parsed.value.fileId, parsed.value.durationMs, !!res.reused));
|
|
17877
|
+
}
|
|
17878
|
+
async function denyUnauthorizedWatch(opts) {
|
|
17879
|
+
const codeName = AGENT_CODE_NAME ?? "unknown";
|
|
17880
|
+
process.stderr.write(
|
|
17881
|
+
`slack-channel(${codeName}): /watch denied \u2014 sender not in SLACK_ALLOWED_USERS, channel ${hashChannelId(opts.channel)}
|
|
17882
|
+
`
|
|
17883
|
+
);
|
|
17884
|
+
await postSlackMessage({
|
|
17885
|
+
channel: opts.channel,
|
|
17886
|
+
text: `\u{1F6AB} \`/watch\` denied \u2014 your Slack user is not in the allowlist for \`${codeName}\`.`,
|
|
17887
|
+
...opts.threadTs ? { thread_ts: opts.threadTs } : {}
|
|
17888
|
+
});
|
|
17889
|
+
}
|
|
17730
17890
|
var trackedThreads = /* @__PURE__ */ new Map();
|
|
17731
17891
|
var THREAD_STORE_PATH = resolveThreadStorePath();
|
|
17732
17892
|
var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAYS);
|
|
@@ -19188,7 +19348,9 @@ async function connectSocketMode() {
|
|
|
19188
19348
|
const helpSuffixed = agentSlashCommand("/help");
|
|
19189
19349
|
const isRestartCommand = strippedText === "/restart" || strippedText.startsWith("/restart ") || strippedText === restartSuffixed || strippedText.startsWith(`${restartSuffixed} `);
|
|
19190
19350
|
const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ") || strippedText === helpSuffixed || strippedText.startsWith(`${helpSuffixed} `);
|
|
19191
|
-
const
|
|
19351
|
+
const isWatchDm = evt.channel?.startsWith("D") ?? false;
|
|
19352
|
+
const isWatchCommand = !isBot && watchArgsFromText(strippedText, AGENT_CODE_NAME, { allowBare: isWatchDm }) !== null;
|
|
19353
|
+
const command = isBot ? void 0 : isHelpCommand ? { command: "help", authorized: true } : isWatchCommand ? { command: "watch", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : isRestartCommand ? { command: "restart", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : void 0;
|
|
19192
19354
|
const slackHomeTeamId = process.env.SLACK_HOME_TEAM_ID;
|
|
19193
19355
|
const sameOrg = !!slackHomeTeamId && evt.team === slackHomeTeamId;
|
|
19194
19356
|
const access = decideInboundAccess({
|
|
@@ -19247,6 +19409,22 @@ async function connectSocketMode() {
|
|
|
19247
19409
|
});
|
|
19248
19410
|
return;
|
|
19249
19411
|
}
|
|
19412
|
+
if (access.command === "watch") {
|
|
19413
|
+
if (!access.authorized) {
|
|
19414
|
+
await denyUnauthorizedWatch({
|
|
19415
|
+
channel: evt.channel ?? "",
|
|
19416
|
+
threadTs: evt.thread_ts
|
|
19417
|
+
});
|
|
19418
|
+
return;
|
|
19419
|
+
}
|
|
19420
|
+
await handleWatchCommand({
|
|
19421
|
+
channel: evt.channel ?? "",
|
|
19422
|
+
threadTs: evt.thread_ts,
|
|
19423
|
+
ts: evt.ts ?? "",
|
|
19424
|
+
rawText: strippedText
|
|
19425
|
+
});
|
|
19426
|
+
return;
|
|
19427
|
+
}
|
|
19250
19428
|
if (access.command === "restart") {
|
|
19251
19429
|
if (!access.authorized) {
|
|
19252
19430
|
await denyUnauthorizedRestart({
|
|
@@ -14868,6 +14868,100 @@ function decideInboundAccess(input) {
|
|
|
14868
14868
|
return { kind: "admit" };
|
|
14869
14869
|
}
|
|
14870
14870
|
|
|
14871
|
+
// src/watch-command.ts
|
|
14872
|
+
var WATCH_DEFAULT_DURATION_MS = 2 * 60 * 60 * 1e3;
|
|
14873
|
+
var WATCH_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14874
|
+
var WATCH_MIN_DURATION_MS = 5 * 60 * 1e3;
|
|
14875
|
+
function extractDriveFileId(s) {
|
|
14876
|
+
const trimmed = s.trim().replace(/^<|>$/g, "");
|
|
14877
|
+
const pathId = trimmed.match(/\/d\/([A-Za-z0-9_-]{10,})/);
|
|
14878
|
+
if (pathId?.[1]) return pathId[1];
|
|
14879
|
+
const idParam = trimmed.match(/[?&]id=([A-Za-z0-9_-]{10,})/);
|
|
14880
|
+
if (idParam?.[1]) return idParam[1];
|
|
14881
|
+
if (!/[/:?]/.test(trimmed) && /^[A-Za-z0-9_-]{20,}$/.test(trimmed)) return trimmed;
|
|
14882
|
+
return null;
|
|
14883
|
+
}
|
|
14884
|
+
function parseDurationToken(token) {
|
|
14885
|
+
const m = token.trim().match(/^(\d+)\s*([smhd])?$/i);
|
|
14886
|
+
if (!m?.[1]) return null;
|
|
14887
|
+
const n = Number.parseInt(m[1], 10);
|
|
14888
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
14889
|
+
const unit = (m[2] ?? "m").toLowerCase();
|
|
14890
|
+
const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
14891
|
+
return n * mult;
|
|
14892
|
+
}
|
|
14893
|
+
function parseWatchArgs(args) {
|
|
14894
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
14895
|
+
const first = parts[0];
|
|
14896
|
+
if (!first) return { ok: false, error: "usage" };
|
|
14897
|
+
if (parts.length > 2) return { ok: false, error: "usage" };
|
|
14898
|
+
const fileId = extractDriveFileId(first);
|
|
14899
|
+
if (!fileId) return { ok: false, error: "bad-url" };
|
|
14900
|
+
let durationMs = WATCH_DEFAULT_DURATION_MS;
|
|
14901
|
+
const durationToken = parts[1];
|
|
14902
|
+
if (durationToken !== void 0) {
|
|
14903
|
+
const parsed = parseDurationToken(durationToken);
|
|
14904
|
+
if (parsed === null) return { ok: false, error: "bad-duration" };
|
|
14905
|
+
durationMs = Math.min(Math.max(parsed, WATCH_MIN_DURATION_MS), WATCH_MAX_DURATION_MS);
|
|
14906
|
+
}
|
|
14907
|
+
return { ok: true, value: { fileId, durationMs } };
|
|
14908
|
+
}
|
|
14909
|
+
async function postWatchTrigger(opts) {
|
|
14910
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
14911
|
+
try {
|
|
14912
|
+
const res = await doFetch(`${opts.host}/host/triggers`, {
|
|
14913
|
+
method: "POST",
|
|
14914
|
+
headers: {
|
|
14915
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
14916
|
+
Authorization: `Bearer ${opts.apiKey}`
|
|
14917
|
+
},
|
|
14918
|
+
body: JSON.stringify({
|
|
14919
|
+
agent_id: opts.agentId,
|
|
14920
|
+
provider: "gdrive_comments",
|
|
14921
|
+
config: { fileId: opts.fileId },
|
|
14922
|
+
expires_at: opts.expiresAtIso
|
|
14923
|
+
}),
|
|
14924
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
14925
|
+
});
|
|
14926
|
+
if (!res.ok) {
|
|
14927
|
+
let msg = `HTTP ${res.status}`;
|
|
14928
|
+
try {
|
|
14929
|
+
const j2 = await res.json();
|
|
14930
|
+
if (j2?.error) msg = j2.error;
|
|
14931
|
+
} catch {
|
|
14932
|
+
}
|
|
14933
|
+
return { ok: false, status: res.status, error: msg };
|
|
14934
|
+
}
|
|
14935
|
+
const j = await res.json().catch(() => ({}));
|
|
14936
|
+
return { ok: true, status: res.status, reused: !!j.reused, expiresAt: j.expires_at ?? null };
|
|
14937
|
+
} catch (err) {
|
|
14938
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
14939
|
+
}
|
|
14940
|
+
}
|
|
14941
|
+
function humanDuration(ms) {
|
|
14942
|
+
const mins = Math.round(ms / 6e4);
|
|
14943
|
+
if (mins % 1440 === 0) return `${mins / 1440}d`;
|
|
14944
|
+
if (mins % 60 === 0) return `${mins / 60}h`;
|
|
14945
|
+
return `${mins}m`;
|
|
14946
|
+
}
|
|
14947
|
+
function watchUsageText() {
|
|
14948
|
+
return "Usage: `/watch <google-doc-url> [duration]` \u2014 e.g. `/watch https://docs.google.com/document/d/\u2026/edit 2h`. Duration defaults to 2h (max 7d).";
|
|
14949
|
+
}
|
|
14950
|
+
function watchBadUrlText() {
|
|
14951
|
+
return "That doesn't look like a Google Doc link. Paste the doc's share URL (the part with `/d/<id>`).";
|
|
14952
|
+
}
|
|
14953
|
+
function watchBadDurationText() {
|
|
14954
|
+
return "I couldn't read that duration. Try `30m`, `2h`, or `1d` (max 7d).";
|
|
14955
|
+
}
|
|
14956
|
+
function watchErrorText() {
|
|
14957
|
+
return "\u274C I couldn't set up that watch just now. Please try again in a moment.";
|
|
14958
|
+
}
|
|
14959
|
+
function watchSuccessTextPlain(fileId, durationMs, reused) {
|
|
14960
|
+
const url = `https://docs.google.com/document/d/${fileId}/edit`;
|
|
14961
|
+
const verb = reused ? "Extended my watch on" : "Watching";
|
|
14962
|
+
return `\u{1F440} ${verb} ${url} for new comments that mention me, for the next ${humanDuration(durationMs)}. I'll pause automatically when the window's up. (Make sure Google Drive is connected with comment access, or I won't see them.)`;
|
|
14963
|
+
}
|
|
14964
|
+
|
|
14871
14965
|
// src/telegram-peer-rate-limiter.ts
|
|
14872
14966
|
var SECOND_MS = 1e3;
|
|
14873
14967
|
var MINUTE_MS = 60 * SECOND_MS;
|
|
@@ -16533,6 +16627,7 @@ function buildTelegramHelpMessage(codeName) {
|
|
|
16533
16627
|
`_Type these in any chat where the bot is present (intercepted by the agent):_`,
|
|
16534
16628
|
`\u2022 /help \u2014 show this help`,
|
|
16535
16629
|
`\u2022 /status \u2014 this agent's model, session origin, uptime + connectivity`,
|
|
16630
|
+
`\u2022 /watch <google-doc-url> [duration] \u2014 watch a Google Doc for comments that mention me (default 2h, max 7d; auto-pauses when the window ends)`,
|
|
16536
16631
|
`\u2022 /restart \u2014 restart this agent`,
|
|
16537
16632
|
`\u2022 /investigate-${codeName} \u2014 live tail of this agent's terminal pane (DM only; team owners/admins and the agent's reports-to person)`
|
|
16538
16633
|
].join("\n");
|
|
@@ -16562,6 +16657,63 @@ async function handleHelpCommand(opts) {
|
|
|
16562
16657
|
);
|
|
16563
16658
|
}
|
|
16564
16659
|
}
|
|
16660
|
+
async function handleWatchCommand(opts) {
|
|
16661
|
+
const reply = async (text) => {
|
|
16662
|
+
try {
|
|
16663
|
+
const resp = await telegramApiCall(
|
|
16664
|
+
"sendMessage",
|
|
16665
|
+
{ chat_id: opts.chatId, text, reply_to_message_id: Number(opts.messageId) },
|
|
16666
|
+
1e4
|
|
16667
|
+
);
|
|
16668
|
+
if (!resp.ok) {
|
|
16669
|
+
process.stderr.write(
|
|
16670
|
+
`telegram-channel(${AGENT_CODE_NAME}): /watch reply rejected (chat ${redactId(opts.chatId)}): ${resp.description ?? "unknown"}
|
|
16671
|
+
`
|
|
16672
|
+
);
|
|
16673
|
+
}
|
|
16674
|
+
} catch (err) {
|
|
16675
|
+
process.stderr.write(
|
|
16676
|
+
`telegram-channel(${AGENT_CODE_NAME}): /watch reply send failed: ${redactAugmentedPaths(err.message)}
|
|
16677
|
+
`
|
|
16678
|
+
);
|
|
16679
|
+
}
|
|
16680
|
+
};
|
|
16681
|
+
const args = opts.text.replace(/^\/watch(?:@[A-Za-z0-9_]{1,64})?\s*/i, "");
|
|
16682
|
+
const parsed = parseWatchArgs(args);
|
|
16683
|
+
if (!parsed.ok) {
|
|
16684
|
+
await reply(
|
|
16685
|
+
parsed.error === "bad-url" ? watchBadUrlText() : parsed.error === "bad-duration" ? watchBadDurationText() : watchUsageText()
|
|
16686
|
+
);
|
|
16687
|
+
return;
|
|
16688
|
+
}
|
|
16689
|
+
if (!AGT_HOST || !AGT_API_KEY || !AGT_AGENT_ID) {
|
|
16690
|
+
process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): /watch missing AGT_* env \u2014 cannot create watch
|
|
16691
|
+
`);
|
|
16692
|
+
await reply(watchErrorText());
|
|
16693
|
+
return;
|
|
16694
|
+
}
|
|
16695
|
+
const expiresAtIso = new Date(Date.now() + parsed.value.durationMs).toISOString();
|
|
16696
|
+
const res = await postWatchTrigger({
|
|
16697
|
+
host: AGT_HOST,
|
|
16698
|
+
apiKey: AGT_API_KEY,
|
|
16699
|
+
agentId: AGT_AGENT_ID,
|
|
16700
|
+
fileId: parsed.value.fileId,
|
|
16701
|
+
expiresAtIso
|
|
16702
|
+
});
|
|
16703
|
+
if (!res.ok) {
|
|
16704
|
+
process.stderr.write(
|
|
16705
|
+
`telegram-channel(${AGENT_CODE_NAME}): /watch create failed (status=${res.status ?? "?"}): ${res.error ?? "unknown"}
|
|
16706
|
+
`
|
|
16707
|
+
);
|
|
16708
|
+
await reply(watchErrorText());
|
|
16709
|
+
return;
|
|
16710
|
+
}
|
|
16711
|
+
process.stderr.write(
|
|
16712
|
+
`telegram-channel(${AGENT_CODE_NAME}): /watch ${res.reused ? "extended" : "created"} for doc ${parsed.value.fileId.slice(0, 8)}\u2026 chat ${redactId(opts.chatId)}
|
|
16713
|
+
`
|
|
16714
|
+
);
|
|
16715
|
+
await reply(watchSuccessTextPlain(parsed.value.fileId, parsed.value.durationMs, !!res.reused));
|
|
16716
|
+
}
|
|
16565
16717
|
function buildTelegramStatusReply() {
|
|
16566
16718
|
const state = readAgentSessionState(TELEGRAM_AGENT_DIR);
|
|
16567
16719
|
return buildAgentConfigReport({
|
|
@@ -16967,6 +17119,10 @@ var STATUS_SYNTAX_RE = /^\/status(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
|
|
|
16967
17119
|
function isStatusSyntax(text) {
|
|
16968
17120
|
return STATUS_SYNTAX_RE.test(text);
|
|
16969
17121
|
}
|
|
17122
|
+
var WATCH_SYNTAX_RE = /^\/watch(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
|
|
17123
|
+
function isWatchSyntax(text) {
|
|
17124
|
+
return WATCH_SYNTAX_RE.test(text);
|
|
17125
|
+
}
|
|
16970
17126
|
function isRestartSyntax(text) {
|
|
16971
17127
|
return RESTART_SYNTAX_RE.test(text);
|
|
16972
17128
|
}
|
|
@@ -18208,7 +18364,7 @@ async function pollLoop() {
|
|
|
18208
18364
|
const trimmedContent = content.trim();
|
|
18209
18365
|
const isFromBot = !!msg.from?.is_bot;
|
|
18210
18366
|
const nowMs = Date.now();
|
|
18211
|
-
const telegramCommand = isHelpSyntax(trimmedContent) ? "help" : isStatusSyntax(trimmedContent) ? "status" : isRestartSyntax(trimmedContent) ? "restart" : isInvestigateSyntax(trimmedContent) ? "investigate" : void 0;
|
|
18367
|
+
const telegramCommand = isHelpSyntax(trimmedContent) ? "help" : isStatusSyntax(trimmedContent) ? "status" : isWatchSyntax(trimmedContent) ? "watch" : isRestartSyntax(trimmedContent) ? "restart" : isInvestigateSyntax(trimmedContent) ? "investigate" : void 0;
|
|
18212
18368
|
let classification;
|
|
18213
18369
|
let peerLeaf;
|
|
18214
18370
|
if (isFromBot) {
|
|
@@ -18288,6 +18444,19 @@ async function pollLoop() {
|
|
|
18288
18444
|
}
|
|
18289
18445
|
continue;
|
|
18290
18446
|
}
|
|
18447
|
+
if (access.kind === "command" && access.command === "watch") {
|
|
18448
|
+
const disposition = await classifyRestartCommand(
|
|
18449
|
+
trimmedContent.replace(/^\/watch(@[A-Za-z0-9_]{1,64})?/, "/restart$1")
|
|
18450
|
+
);
|
|
18451
|
+
if (disposition === "act") {
|
|
18452
|
+
await handleWatchCommand({
|
|
18453
|
+
chatId,
|
|
18454
|
+
messageId: String(msg.message_id),
|
|
18455
|
+
text: trimmedContent
|
|
18456
|
+
});
|
|
18457
|
+
}
|
|
18458
|
+
continue;
|
|
18459
|
+
}
|
|
18291
18460
|
if (access.kind === "command" && access.command === "restart") {
|
|
18292
18461
|
const disposition = await classifyRestartCommand(trimmedContent);
|
|
18293
18462
|
if (disposition === "act") {
|
package/package.json
CHANGED
|
File without changes
|