clisbot 0.1.19 → 0.1.21
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 +5 -2
- package/dist/main.js +309 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,8 +64,11 @@ If you want to try first without persisting the token yet, just remove `--persis
|
|
|
64
64
|
Current auth note:
|
|
65
65
|
|
|
66
66
|
- DMs currently start in pairing mode by default.
|
|
67
|
-
-
|
|
68
|
-
- Today, if you want an owner or app admin, grant that principal explicitly with
|
|
67
|
+
- If no app owner is configured yet, the first DM user during the first `ownerClaimWindowMinutes` becomes app `owner` automatically and does not need pairing approval.
|
|
68
|
+
- Today, if you want an owner or app admin, grant that principal explicitly with the platform prefix plus the channel-native user id, for example `telegram:1276408333` or `slack:U123ABC456`.
|
|
69
|
+
- Example commands:
|
|
70
|
+
- `clisbot auth add-user app --role owner --user telegram:1276408333`
|
|
71
|
+
- `clisbot auth add-user app --role admin --user slack:U123ABC456`
|
|
69
72
|
- `clisbot auth --help` now covers role scopes, permission sets, and add/remove flows for users and permissions.
|
|
70
73
|
- App-level auth and owner-claim semantics in [Authorization And Roles](docs/user-guide/auth-and-roles.md) describe both the current runtime reality and the remaining target-model gaps.
|
|
71
74
|
|
package/dist/main.js
CHANGED
|
@@ -54487,6 +54487,9 @@ function renderPairingSetupHelpLines(prefix = "", options = {}) {
|
|
|
54487
54487
|
lines.push(`${prefix} - Approve the returned Slack code with: \`clisbot pairing approve slack <code>\``);
|
|
54488
54488
|
}
|
|
54489
54489
|
lines.push(`${prefix} - Configured app owner/admin principals bypass pairing in DMs.`);
|
|
54490
|
+
if (options.ownerConfigured === false) {
|
|
54491
|
+
lines.push(`${prefix} - If no owner is configured yet, the first DM user during the first ${options.ownerClaimWindowMinutes ?? 30} minutes becomes app owner automatically.`);
|
|
54492
|
+
}
|
|
54490
54493
|
return lines;
|
|
54491
54494
|
}
|
|
54492
54495
|
function renderTmuxDebugHelpLines(prefix = "") {
|
|
@@ -61890,6 +61893,8 @@ function getExecutableNames(command) {
|
|
|
61890
61893
|
// src/runners/tmux/client.ts
|
|
61891
61894
|
var MAIN_WINDOW_NAME = "main";
|
|
61892
61895
|
var TMUX_NOT_FOUND_CODE = "ENOENT";
|
|
61896
|
+
var TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS = 1000;
|
|
61897
|
+
var TMUX_SERVER_BOOTSTRAP_POLL_MS = 25;
|
|
61893
61898
|
var TMUX_SERVER_DEFAULTS = [
|
|
61894
61899
|
["exit-empty", "off"],
|
|
61895
61900
|
["destroy-unattached", "off"]
|
|
@@ -61900,6 +61905,9 @@ class TmuxClient {
|
|
|
61900
61905
|
constructor(socketPath) {
|
|
61901
61906
|
this.socketPath = socketPath;
|
|
61902
61907
|
}
|
|
61908
|
+
isServerUnavailableOutput(output) {
|
|
61909
|
+
return /no server running|No such file or directory|failed to connect to server|error connecting to/i.test(output);
|
|
61910
|
+
}
|
|
61903
61911
|
async exec(args, options = {}) {
|
|
61904
61912
|
if (!commandExists("tmux")) {
|
|
61905
61913
|
throw new Error("tmux is not installed or not available on PATH. Install tmux and restart clisbot.");
|
|
@@ -61951,7 +61959,10 @@ class TmuxClient {
|
|
|
61951
61959
|
}
|
|
61952
61960
|
const output = `${result.stderr}
|
|
61953
61961
|
${result.stdout}`.trim();
|
|
61954
|
-
|
|
61962
|
+
if (this.isServerUnavailableOutput(output)) {
|
|
61963
|
+
return false;
|
|
61964
|
+
}
|
|
61965
|
+
return false;
|
|
61955
61966
|
}
|
|
61956
61967
|
async ensureServerDefaults() {
|
|
61957
61968
|
if (!await this.isServerRunning()) {
|
|
@@ -61961,6 +61972,35 @@ ${result.stdout}`.trim();
|
|
|
61961
61972
|
await this.execOrThrow(["set-option", "-g", name, value]);
|
|
61962
61973
|
}
|
|
61963
61974
|
}
|
|
61975
|
+
isBootstrapRetryableError(error) {
|
|
61976
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61977
|
+
return this.isServerUnavailableOutput(message);
|
|
61978
|
+
}
|
|
61979
|
+
async withServerBootstrapRetry(task) {
|
|
61980
|
+
const deadline = Date.now() + TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS;
|
|
61981
|
+
while (true) {
|
|
61982
|
+
try {
|
|
61983
|
+
return await task();
|
|
61984
|
+
} catch (error) {
|
|
61985
|
+
if (!this.isBootstrapRetryableError(error) || Date.now() >= deadline) {
|
|
61986
|
+
throw error;
|
|
61987
|
+
}
|
|
61988
|
+
await sleep(TMUX_SERVER_BOOTSTRAP_POLL_MS);
|
|
61989
|
+
}
|
|
61990
|
+
}
|
|
61991
|
+
}
|
|
61992
|
+
async waitForSessionBootstrap(sessionName) {
|
|
61993
|
+
const deadline = Date.now() + TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS;
|
|
61994
|
+
while (true) {
|
|
61995
|
+
if (await this.hasSession(sessionName)) {
|
|
61996
|
+
return;
|
|
61997
|
+
}
|
|
61998
|
+
if (Date.now() >= deadline) {
|
|
61999
|
+
throw new Error(`tmux session "${sessionName}" did not become reachable on socket ${this.socketPath} within ${TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS}ms.`);
|
|
62000
|
+
}
|
|
62001
|
+
await sleep(TMUX_SERVER_BOOTSTRAP_POLL_MS);
|
|
62002
|
+
}
|
|
62003
|
+
}
|
|
61964
62004
|
async newSession(params) {
|
|
61965
62005
|
await this.execOrThrow([
|
|
61966
62006
|
"new-session",
|
|
@@ -61973,8 +62013,13 @@ ${result.stdout}`.trim();
|
|
|
61973
62013
|
params.cwd,
|
|
61974
62014
|
params.command
|
|
61975
62015
|
]);
|
|
61976
|
-
await this.
|
|
61977
|
-
await this.
|
|
62016
|
+
await this.waitForSessionBootstrap(params.sessionName);
|
|
62017
|
+
await this.withServerBootstrapRetry(async () => {
|
|
62018
|
+
await this.ensureServerDefaults();
|
|
62019
|
+
});
|
|
62020
|
+
await this.withServerBootstrapRetry(async () => {
|
|
62021
|
+
await this.freezeWindowName(`${params.sessionName}:${MAIN_WINDOW_NAME}`);
|
|
62022
|
+
});
|
|
61978
62023
|
}
|
|
61979
62024
|
async newWindow(params) {
|
|
61980
62025
|
const paneId = await this.execOrThrow([
|
|
@@ -66481,6 +66526,7 @@ async function monitorTmuxRun(params) {
|
|
|
66481
66526
|
promptSubmitDelayMs: params.promptSubmitDelayMs,
|
|
66482
66527
|
timingContext: params.timingContext
|
|
66483
66528
|
});
|
|
66529
|
+
await params.onPromptSubmitted?.();
|
|
66484
66530
|
logLatencyDebug("tmux-submit-complete", params.timingContext, {
|
|
66485
66531
|
sessionName: params.sessionName,
|
|
66486
66532
|
promptSubmitDelayMs: params.promptSubmitDelayMs,
|
|
@@ -66655,7 +66701,8 @@ class ActiveRunManager {
|
|
|
66655
66701
|
observers: new Map,
|
|
66656
66702
|
observerFailures: new Map,
|
|
66657
66703
|
initialResult,
|
|
66658
|
-
latestUpdate: update
|
|
66704
|
+
latestUpdate: update,
|
|
66705
|
+
steeringReady: true
|
|
66659
66706
|
});
|
|
66660
66707
|
this.startRunMonitor(resolved.sessionKey, {
|
|
66661
66708
|
prompt: undefined,
|
|
@@ -66700,7 +66747,8 @@ class ActiveRunManager {
|
|
|
66700
66747
|
fullSnapshot: "",
|
|
66701
66748
|
initialSnapshot: "",
|
|
66702
66749
|
note: "Starting runner session..."
|
|
66703
|
-
})
|
|
66750
|
+
}),
|
|
66751
|
+
steeringReady: false
|
|
66704
66752
|
});
|
|
66705
66753
|
try {
|
|
66706
66754
|
const { resolved, initialSnapshot } = await this.runnerSessions.preparePromptSession(target, {
|
|
@@ -66794,6 +66842,9 @@ class ActiveRunManager {
|
|
|
66794
66842
|
hasActiveRun(target) {
|
|
66795
66843
|
return this.activeRuns.has(target.sessionKey);
|
|
66796
66844
|
}
|
|
66845
|
+
canSteerActiveRun(target) {
|
|
66846
|
+
return this.activeRuns.get(target.sessionKey)?.steeringReady ?? false;
|
|
66847
|
+
}
|
|
66797
66848
|
async stop() {
|
|
66798
66849
|
this.stopping = true;
|
|
66799
66850
|
const activeRuns = [...this.activeRuns.values()];
|
|
@@ -66898,6 +66949,9 @@ class ActiveRunManager {
|
|
|
66898
66949
|
}
|
|
66899
66950
|
(async () => {
|
|
66900
66951
|
try {
|
|
66952
|
+
if (!params.prompt) {
|
|
66953
|
+
run.steeringReady = true;
|
|
66954
|
+
}
|
|
66901
66955
|
await monitorTmuxRun({
|
|
66902
66956
|
tmux: this.tmux,
|
|
66903
66957
|
sessionName: run.resolved.sessionName,
|
|
@@ -66912,6 +66966,13 @@ class ActiveRunManager {
|
|
|
66912
66966
|
initialSnapshot: params.initialSnapshot,
|
|
66913
66967
|
detachedAlready: params.detachedAlready,
|
|
66914
66968
|
timingContext: params.timingContext,
|
|
66969
|
+
onPromptSubmitted: async () => {
|
|
66970
|
+
const currentRun = this.activeRuns.get(sessionKey);
|
|
66971
|
+
if (!currentRun) {
|
|
66972
|
+
return;
|
|
66973
|
+
}
|
|
66974
|
+
currentRun.steeringReady = true;
|
|
66975
|
+
},
|
|
66915
66976
|
onRunning: async (update) => {
|
|
66916
66977
|
const currentRun = this.activeRuns.get(sessionKey);
|
|
66917
66978
|
if (!currentRun) {
|
|
@@ -67094,6 +67155,9 @@ class AgentService {
|
|
|
67094
67155
|
hasActiveRun(target) {
|
|
67095
67156
|
return this.activeRuns.hasActiveRun(target);
|
|
67096
67157
|
}
|
|
67158
|
+
canSteerActiveRun(target) {
|
|
67159
|
+
return this.activeRuns.canSteerActiveRun(target);
|
|
67160
|
+
}
|
|
67097
67161
|
async submitSessionInput(target, text) {
|
|
67098
67162
|
return this.runnerSessions.submitSessionInput(target, text);
|
|
67099
67163
|
}
|
|
@@ -68207,6 +68271,28 @@ function renderTranscriptDisabledMessage() {
|
|
|
68207
68271
|
].join(`
|
|
68208
68272
|
`);
|
|
68209
68273
|
}
|
|
68274
|
+
function renderStartupSteeringUnavailableMessage() {
|
|
68275
|
+
return [
|
|
68276
|
+
"The active run is still starting and cannot accept steering input yet.",
|
|
68277
|
+
"Send a normal follow-up message to keep it ordered behind the first prompt, or wait until startup finishes before using `/steer`."
|
|
68278
|
+
].join(`
|
|
68279
|
+
`);
|
|
68280
|
+
}
|
|
68281
|
+
function renderPrincipalFormat(identity) {
|
|
68282
|
+
if (identity.platform === "slack") {
|
|
68283
|
+
return "slack:<nativeUserId>";
|
|
68284
|
+
}
|
|
68285
|
+
return "telegram:<nativeUserId>";
|
|
68286
|
+
}
|
|
68287
|
+
function renderPrincipalExample(identity) {
|
|
68288
|
+
if (identity.senderId) {
|
|
68289
|
+
return `${identity.platform}:${identity.senderId}`;
|
|
68290
|
+
}
|
|
68291
|
+
if (identity.platform === "slack") {
|
|
68292
|
+
return "slack:U123ABC456";
|
|
68293
|
+
}
|
|
68294
|
+
return "telegram:1276408333";
|
|
68295
|
+
}
|
|
68210
68296
|
function renderWhoAmIMessage(params) {
|
|
68211
68297
|
const lines = [
|
|
68212
68298
|
"Who am I",
|
|
@@ -68231,7 +68317,7 @@ function renderWhoAmIMessage(params) {
|
|
|
68231
68317
|
if (params.identity.topicId) {
|
|
68232
68318
|
lines.push(`topicId: \`${params.identity.topicId}\``);
|
|
68233
68319
|
}
|
|
68234
|
-
lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayBypassPairing: \`${params.auth.mayBypassPairing}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `verbose: \`${params.route.verbose}\``);
|
|
68320
|
+
lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayBypassPairing: \`${params.auth.mayBypassPairing}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `verbose: \`${params.route.verbose}\``);
|
|
68235
68321
|
return lines.join(`
|
|
68236
68322
|
`);
|
|
68237
68323
|
}
|
|
@@ -68259,7 +68345,7 @@ function renderRouteStatusMessage(params) {
|
|
|
68259
68345
|
if (params.identity.topicId) {
|
|
68260
68346
|
lines.push(`topicId: \`${params.identity.topicId}\``);
|
|
68261
68347
|
}
|
|
68262
|
-
lines.push(`streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `
|
|
68348
|
+
lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
|
|
68263
68349
|
if (params.runtimeState.startedAt) {
|
|
68264
68350
|
lines.push(`run.startedAt: \`${new Date(params.runtimeState.startedAt).toISOString()}\``);
|
|
68265
68351
|
}
|
|
@@ -68921,6 +69007,7 @@ async function processChannelInteraction(params) {
|
|
|
68921
69007
|
const explicitQueueMessage = slashCommand?.type === "queue" ? slashCommand.text.trim() : undefined;
|
|
68922
69008
|
const explicitSteerMessage = slashCommand?.type === "steer" ? slashCommand.text.trim() : undefined;
|
|
68923
69009
|
const sessionBusy = await (params.agentService.isAwaitingFollowUpRouting?.(params.sessionTarget) ?? params.agentService.isSessionBusy?.(params.sessionTarget) ?? false);
|
|
69010
|
+
const canSteerActiveRun = params.agentService.canSteerActiveRun?.(params.sessionTarget) ?? !sessionBusy;
|
|
68924
69011
|
const queueByMode = !explicitQueueMessage && params.route.additionalMessageMode === "queue" && sessionBusy;
|
|
68925
69012
|
const forceQueuedDelivery = typeof explicitQueueMessage === "string" || queueByMode;
|
|
68926
69013
|
const delayedPromptText = explicitQueueMessage ? params.agentPromptBuilder ? params.agentPromptBuilder(explicitQueueMessage) : explicitQueueMessage : params.agentPromptText ?? params.text;
|
|
@@ -69342,6 +69429,11 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
69342
69429
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
69343
69430
|
return interactionResult;
|
|
69344
69431
|
}
|
|
69432
|
+
if (!canSteerActiveRun) {
|
|
69433
|
+
await params.postText(renderStartupSteeringUnavailableMessage());
|
|
69434
|
+
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
69435
|
+
return interactionResult;
|
|
69436
|
+
}
|
|
69345
69437
|
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringMessage(explicitSteerMessage, params.protectedControlMutationRule));
|
|
69346
69438
|
await params.postText("Steered.");
|
|
69347
69439
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
@@ -69350,7 +69442,7 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
69350
69442
|
};
|
|
69351
69443
|
}
|
|
69352
69444
|
if (!forceQueuedDelivery && params.route.additionalMessageMode === "steer") {
|
|
69353
|
-
if (sessionBusy) {
|
|
69445
|
+
if (sessionBusy && canSteerActiveRun) {
|
|
69354
69446
|
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringMessage(params.text, params.protectedControlMutationRule));
|
|
69355
69447
|
return {
|
|
69356
69448
|
processingIndicatorLifecycle: "active-run"
|
|
@@ -69443,7 +69535,7 @@ function mergeRoleRecord2(defaults, overrides) {
|
|
|
69443
69535
|
}
|
|
69444
69536
|
return merged;
|
|
69445
69537
|
}
|
|
69446
|
-
function
|
|
69538
|
+
function normalizeAuthPrincipal(principal) {
|
|
69447
69539
|
const trimmed = principal.trim();
|
|
69448
69540
|
if (!trimmed) {
|
|
69449
69541
|
return "";
|
|
@@ -69461,17 +69553,17 @@ function normalizePrincipal(principal) {
|
|
|
69461
69553
|
return `${platform}:${userId.trim()}`;
|
|
69462
69554
|
}
|
|
69463
69555
|
function normalizeRoleUsers(users) {
|
|
69464
|
-
return (users ?? []).map(
|
|
69556
|
+
return (users ?? []).map(normalizeAuthPrincipal).filter(Boolean);
|
|
69465
69557
|
}
|
|
69466
|
-
function
|
|
69558
|
+
function resolveAuthPrincipal(identity) {
|
|
69467
69559
|
const senderId = identity.senderId?.trim();
|
|
69468
69560
|
if (!senderId) {
|
|
69469
69561
|
return;
|
|
69470
69562
|
}
|
|
69471
69563
|
if (identity.platform === "slack") {
|
|
69472
|
-
return
|
|
69564
|
+
return normalizeAuthPrincipal(`slack:${senderId}`);
|
|
69473
69565
|
}
|
|
69474
|
-
return
|
|
69566
|
+
return normalizeAuthPrincipal(`telegram:${senderId}`);
|
|
69475
69567
|
}
|
|
69476
69568
|
function findExplicitRole(roles, principal) {
|
|
69477
69569
|
if (!principal || !roles) {
|
|
@@ -69503,7 +69595,7 @@ function hasAppPermission(config, appRole, permission) {
|
|
|
69503
69595
|
return getAllowedPermissions(config.app.auth.roles, appRole).has(permission);
|
|
69504
69596
|
}
|
|
69505
69597
|
function resolveChannelAuth(params) {
|
|
69506
|
-
const principal =
|
|
69598
|
+
const principal = resolveAuthPrincipal(params.identity);
|
|
69507
69599
|
const appAuth = params.config.app.auth;
|
|
69508
69600
|
const explicitAppRole = findExplicitRole(appAuth.roles, principal);
|
|
69509
69601
|
const appRole = explicitAppRole ?? appAuth.defaultRole;
|
|
@@ -69523,6 +69615,128 @@ function resolveChannelAuth(params) {
|
|
|
69523
69615
|
};
|
|
69524
69616
|
}
|
|
69525
69617
|
|
|
69618
|
+
// src/auth/owner-claim.ts
|
|
69619
|
+
var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
|
|
69620
|
+
var OWNER_CLAIM_RUNTIME_STARTED_AT_MS = Date.now();
|
|
69621
|
+
var CONFIG_LOCK_OPTIONS = {
|
|
69622
|
+
retries: {
|
|
69623
|
+
retries: 10,
|
|
69624
|
+
factor: 2,
|
|
69625
|
+
minTimeout: 50,
|
|
69626
|
+
maxTimeout: 2000,
|
|
69627
|
+
randomize: true
|
|
69628
|
+
},
|
|
69629
|
+
stale: 30000
|
|
69630
|
+
};
|
|
69631
|
+
var ownerClaimRuntimeState = {
|
|
69632
|
+
initialized: false,
|
|
69633
|
+
armed: false,
|
|
69634
|
+
closed: false,
|
|
69635
|
+
openedAtMs: OWNER_CLAIM_RUNTIME_STARTED_AT_MS,
|
|
69636
|
+
windowMs: 0
|
|
69637
|
+
};
|
|
69638
|
+
function getOwnerUsers(config) {
|
|
69639
|
+
return config.app.auth.roles.owner?.users ?? [];
|
|
69640
|
+
}
|
|
69641
|
+
function hasConfiguredOwner(config) {
|
|
69642
|
+
return getOwnerUsers(config).some((entry) => entry.trim().length > 0);
|
|
69643
|
+
}
|
|
69644
|
+
function syncRuntimeStateWithConfig(config) {
|
|
69645
|
+
if (hasConfiguredOwner(config)) {
|
|
69646
|
+
ownerClaimRuntimeState.closed = true;
|
|
69647
|
+
}
|
|
69648
|
+
}
|
|
69649
|
+
function primeOwnerClaimRuntime(config, nowMs = OWNER_CLAIM_RUNTIME_STARTED_AT_MS) {
|
|
69650
|
+
if (!ownerClaimRuntimeState.initialized) {
|
|
69651
|
+
ownerClaimRuntimeState.initialized = true;
|
|
69652
|
+
ownerClaimRuntimeState.armed = !hasConfiguredOwner(config);
|
|
69653
|
+
ownerClaimRuntimeState.closed = !ownerClaimRuntimeState.armed;
|
|
69654
|
+
ownerClaimRuntimeState.openedAtMs = nowMs;
|
|
69655
|
+
ownerClaimRuntimeState.windowMs = Math.max(0, config.app.auth.ownerClaimWindowMinutes * 60000);
|
|
69656
|
+
}
|
|
69657
|
+
syncRuntimeStateWithConfig(config);
|
|
69658
|
+
return { ...ownerClaimRuntimeState };
|
|
69659
|
+
}
|
|
69660
|
+
function isOwnerClaimOpen(config, nowMs = Date.now()) {
|
|
69661
|
+
primeOwnerClaimRuntime(config);
|
|
69662
|
+
syncRuntimeStateWithConfig(config);
|
|
69663
|
+
if (ownerClaimRuntimeState.closed || !ownerClaimRuntimeState.armed) {
|
|
69664
|
+
return false;
|
|
69665
|
+
}
|
|
69666
|
+
return nowMs - ownerClaimRuntimeState.openedAtMs <= ownerClaimRuntimeState.windowMs;
|
|
69667
|
+
}
|
|
69668
|
+
function syncOwnerUsers(target, source) {
|
|
69669
|
+
target.app.auth.roles.owner = {
|
|
69670
|
+
...target.app.auth.roles.owner,
|
|
69671
|
+
allow: [...source.app.auth.roles.owner?.allow ?? target.app.auth.roles.owner?.allow ?? []],
|
|
69672
|
+
users: [...source.app.auth.roles.owner?.users ?? []]
|
|
69673
|
+
};
|
|
69674
|
+
}
|
|
69675
|
+
async function withConfigLock(configPath, fn) {
|
|
69676
|
+
const expandedPath = await ensureEditableConfigFile(configPath);
|
|
69677
|
+
let release;
|
|
69678
|
+
try {
|
|
69679
|
+
release = await import_proper_lockfile2.default.lock(expandedPath, CONFIG_LOCK_OPTIONS);
|
|
69680
|
+
return await fn(expandedPath);
|
|
69681
|
+
} finally {
|
|
69682
|
+
if (release) {
|
|
69683
|
+
try {
|
|
69684
|
+
await release();
|
|
69685
|
+
} catch {}
|
|
69686
|
+
}
|
|
69687
|
+
}
|
|
69688
|
+
}
|
|
69689
|
+
async function claimFirstOwnerFromDirectMessage(params) {
|
|
69690
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
69691
|
+
primeOwnerClaimRuntime(params.config);
|
|
69692
|
+
if (params.identity.conversationKind !== "dm") {
|
|
69693
|
+
return { claimed: false, principal: undefined };
|
|
69694
|
+
}
|
|
69695
|
+
const principal = resolveAuthPrincipal(params.identity);
|
|
69696
|
+
if (!principal || !isOwnerClaimOpen(params.config, nowMs)) {
|
|
69697
|
+
return { claimed: false, principal };
|
|
69698
|
+
}
|
|
69699
|
+
const result = await withConfigLock(params.configPath, async (expandedPath) => {
|
|
69700
|
+
const { config: freshConfig } = await readEditableConfig(expandedPath);
|
|
69701
|
+
primeOwnerClaimRuntime(freshConfig);
|
|
69702
|
+
syncRuntimeStateWithConfig(freshConfig);
|
|
69703
|
+
if (!isOwnerClaimOpen(freshConfig, nowMs)) {
|
|
69704
|
+
syncOwnerUsers(params.config, freshConfig);
|
|
69705
|
+
return { claimed: false, principal, configPath: expandedPath };
|
|
69706
|
+
}
|
|
69707
|
+
const currentOwners = getOwnerUsers(freshConfig).map((entry) => entry.trim()).filter(Boolean);
|
|
69708
|
+
if (currentOwners.includes(principal)) {
|
|
69709
|
+
syncOwnerUsers(params.config, freshConfig);
|
|
69710
|
+
ownerClaimRuntimeState.closed = true;
|
|
69711
|
+
return { claimed: false, principal, configPath: expandedPath };
|
|
69712
|
+
}
|
|
69713
|
+
freshConfig.app.auth.roles.owner.users = [...currentOwners, principal];
|
|
69714
|
+
await writeEditableConfig(expandedPath, freshConfig);
|
|
69715
|
+
syncOwnerUsers(params.config, freshConfig);
|
|
69716
|
+
ownerClaimRuntimeState.closed = true;
|
|
69717
|
+
console.log(`clisbot auto-claimed first owner ${principal}`);
|
|
69718
|
+
return { claimed: true, principal, configPath: expandedPath };
|
|
69719
|
+
});
|
|
69720
|
+
return result;
|
|
69721
|
+
}
|
|
69722
|
+
function renderFirstOwnerClaimMessage(params) {
|
|
69723
|
+
return [
|
|
69724
|
+
"First owner claim complete.",
|
|
69725
|
+
"",
|
|
69726
|
+
`principal: \`${params.principal}\``,
|
|
69727
|
+
"role: `owner`",
|
|
69728
|
+
`reason: no owner was configured, and this was the first direct message received during the first ${params.ownerClaimWindowMinutes} minutes after runtime start`,
|
|
69729
|
+
"pairing: not required for you anymore because app owners bypass DM pairing",
|
|
69730
|
+
"",
|
|
69731
|
+
"You can now:",
|
|
69732
|
+
"- chat without pairing approval",
|
|
69733
|
+
"- use full app-level control",
|
|
69734
|
+
"- manage auth, channels, and agent settings",
|
|
69735
|
+
"- use admin-level actions across all agents and routed surfaces"
|
|
69736
|
+
].join(`
|
|
69737
|
+
`);
|
|
69738
|
+
}
|
|
69739
|
+
|
|
69526
69740
|
// src/channels/slack/session-routing.ts
|
|
69527
69741
|
function resolveSlackConversationTarget(params) {
|
|
69528
69742
|
const sessionConfig = params.loadedConfig.raw.session;
|
|
@@ -70731,21 +70945,48 @@ class SlackSocketService {
|
|
|
70731
70945
|
if (params.conversationKind === "dm") {
|
|
70732
70946
|
const directUserId = typeof event.user === "string" ? event.user.trim() : "";
|
|
70733
70947
|
const dmConfig = this.loadedConfig.raw.channels.slack.directMessages;
|
|
70734
|
-
const
|
|
70735
|
-
|
|
70736
|
-
|
|
70737
|
-
|
|
70738
|
-
|
|
70739
|
-
|
|
70740
|
-
senderId: directUserId || undefined,
|
|
70741
|
-
channelId
|
|
70742
|
-
}
|
|
70743
|
-
});
|
|
70948
|
+
const dmIdentity = {
|
|
70949
|
+
platform: "slack",
|
|
70950
|
+
conversationKind: params.conversationKind,
|
|
70951
|
+
senderId: directUserId || undefined,
|
|
70952
|
+
channelId
|
|
70953
|
+
};
|
|
70744
70954
|
if (!directUserId || dmConfig.policy === "disabled") {
|
|
70745
70955
|
debugSlackEvent("drop-dm-disabled", { eventId, directUserId });
|
|
70746
70956
|
await this.processedEventsStore.markCompleted(eventId);
|
|
70747
70957
|
return;
|
|
70748
70958
|
}
|
|
70959
|
+
let ownerClaimed = false;
|
|
70960
|
+
let ownerPrincipal;
|
|
70961
|
+
try {
|
|
70962
|
+
const claimResult = await claimFirstOwnerFromDirectMessage({
|
|
70963
|
+
config: this.loadedConfig.raw,
|
|
70964
|
+
configPath: this.loadedConfig.configPath,
|
|
70965
|
+
identity: dmIdentity
|
|
70966
|
+
});
|
|
70967
|
+
ownerClaimed = claimResult.claimed;
|
|
70968
|
+
ownerPrincipal = claimResult.principal;
|
|
70969
|
+
} catch (error) {
|
|
70970
|
+
console.error("slack first-owner claim failed", error);
|
|
70971
|
+
}
|
|
70972
|
+
if (ownerClaimed && ownerPrincipal) {
|
|
70973
|
+
try {
|
|
70974
|
+
await postSlackText(this.app.client, {
|
|
70975
|
+
channel: channelId,
|
|
70976
|
+
text: renderFirstOwnerClaimMessage({
|
|
70977
|
+
principal: ownerPrincipal,
|
|
70978
|
+
ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
70979
|
+
})
|
|
70980
|
+
});
|
|
70981
|
+
} catch (error) {
|
|
70982
|
+
console.error("slack first-owner claim reply failed", error);
|
|
70983
|
+
}
|
|
70984
|
+
}
|
|
70985
|
+
const auth2 = resolveChannelAuth({
|
|
70986
|
+
config: this.loadedConfig.raw,
|
|
70987
|
+
agentId: params.route.agentId,
|
|
70988
|
+
identity: dmIdentity
|
|
70989
|
+
});
|
|
70749
70990
|
if (dmConfig.policy !== "open" && !auth2.mayBypassPairing) {
|
|
70750
70991
|
const storedAllowFrom = await readChannelAllowFromStore("slack");
|
|
70751
70992
|
const allowed = isSlackSenderAllowed({
|
|
@@ -72256,20 +72497,47 @@ class TelegramPollingService {
|
|
|
72256
72497
|
const directMessages = this.loadedConfig.raw.channels.telegram.directMessages;
|
|
72257
72498
|
const senderId = message.from?.id != null ? String(message.from.id) : "";
|
|
72258
72499
|
const senderUsername = message.from?.username;
|
|
72259
|
-
const
|
|
72260
|
-
|
|
72261
|
-
|
|
72262
|
-
|
|
72263
|
-
|
|
72264
|
-
|
|
72265
|
-
senderId: senderId || undefined,
|
|
72266
|
-
chatId: String(message.chat.id)
|
|
72267
|
-
}
|
|
72268
|
-
});
|
|
72500
|
+
const dmIdentity = {
|
|
72501
|
+
platform: "telegram",
|
|
72502
|
+
conversationKind: routeInfo.conversationKind,
|
|
72503
|
+
senderId: senderId || undefined,
|
|
72504
|
+
chatId: String(message.chat.id)
|
|
72505
|
+
};
|
|
72269
72506
|
if (!senderId || directMessages.policy === "disabled") {
|
|
72270
72507
|
await this.processedEventsStore.markCompleted(eventId);
|
|
72271
72508
|
return;
|
|
72272
72509
|
}
|
|
72510
|
+
let ownerClaimed = false;
|
|
72511
|
+
let ownerPrincipal;
|
|
72512
|
+
try {
|
|
72513
|
+
const claimResult = await claimFirstOwnerFromDirectMessage({
|
|
72514
|
+
config: this.loadedConfig.raw,
|
|
72515
|
+
configPath: this.loadedConfig.configPath,
|
|
72516
|
+
identity: dmIdentity
|
|
72517
|
+
});
|
|
72518
|
+
ownerClaimed = claimResult.claimed;
|
|
72519
|
+
ownerPrincipal = claimResult.principal;
|
|
72520
|
+
} catch (error) {
|
|
72521
|
+
console.error("telegram first-owner claim failed", error);
|
|
72522
|
+
}
|
|
72523
|
+
if (ownerClaimed && ownerPrincipal) {
|
|
72524
|
+
try {
|
|
72525
|
+
await callTelegramApi(this.accountConfig.botToken, "sendMessage", {
|
|
72526
|
+
chat_id: message.chat.id,
|
|
72527
|
+
text: renderFirstOwnerClaimMessage({
|
|
72528
|
+
principal: ownerPrincipal,
|
|
72529
|
+
ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
72530
|
+
})
|
|
72531
|
+
});
|
|
72532
|
+
} catch (error) {
|
|
72533
|
+
console.error("telegram first-owner claim reply failed", error);
|
|
72534
|
+
}
|
|
72535
|
+
}
|
|
72536
|
+
const auth = resolveChannelAuth({
|
|
72537
|
+
config: this.loadedConfig.raw,
|
|
72538
|
+
agentId: routeInfo.route.agentId,
|
|
72539
|
+
identity: dmIdentity
|
|
72540
|
+
});
|
|
72273
72541
|
if (directMessages.policy !== "open" && !auth.mayBypassPairing) {
|
|
72274
72542
|
const storedAllowFrom = await readChannelAllowFromStore("telegram");
|
|
72275
72543
|
const allowed = isTelegramSenderAllowed({
|
|
@@ -73293,6 +73561,7 @@ class RuntimeSupervisor {
|
|
|
73293
73561
|
}
|
|
73294
73562
|
async createRuntime(loadedConfig) {
|
|
73295
73563
|
const runtimeId = this.nextRuntimeId++;
|
|
73564
|
+
primeOwnerClaimRuntime(loadedConfig.raw);
|
|
73296
73565
|
const agentService = this.dependencies.createAgentService(loadedConfig);
|
|
73297
73566
|
const processedEventsStore = this.dependencies.createProcessedEventsStore(loadedConfig.processedEventsPath);
|
|
73298
73567
|
const activityStore = this.dependencies.createActivityStore();
|
|
@@ -73753,7 +74022,8 @@ function appendAuthOnboardingLines(lines, summary, prefix = "") {
|
|
|
73753
74022
|
lines.push(`${prefix}Auth onboarding:`);
|
|
73754
74023
|
if (!hasConfiguredPrivilegedPrincipal(summary)) {
|
|
73755
74024
|
lines.push(`${prefix} - get the principal from a surface the bot can already see; Telegram groups or topics can use \`/whoami\` before routing, while DMs with pairing must pair first`);
|
|
73756
|
-
lines.push(`${prefix} -
|
|
74025
|
+
lines.push(`${prefix} - if no owner exists yet, the first DM user during the first ${summary.ownerSummary.ownerClaimWindowMinutes} minutes becomes app owner automatically`);
|
|
74026
|
+
lines.push(`${prefix} - after the first owner exists, add more principals with: \`clisbot auth add-user app --role <owner|admin> --user <principal>\``);
|
|
73757
74027
|
} else {
|
|
73758
74028
|
lines.push(`${prefix} - inspect current app roles with: \`clisbot auth show app\``);
|
|
73759
74029
|
}
|
|
@@ -73874,6 +74144,8 @@ function renderStartSummary(summary) {
|
|
|
73874
74144
|
lines.push(...renderPairingSetupHelpLines(" ", {
|
|
73875
74145
|
slackEnabled: summary.channelSummaries.some((channel) => channel.channel === "slack" && channel.enabled),
|
|
73876
74146
|
telegramEnabled: summary.channelSummaries.some((channel) => channel.channel === "telegram" && channel.enabled),
|
|
74147
|
+
ownerConfigured: hasConfiguredPrivilegedPrincipal(summary),
|
|
74148
|
+
ownerClaimWindowMinutes: summary.ownerSummary.ownerClaimWindowMinutes,
|
|
73877
74149
|
slackDirectMessagesPolicy: summary.channelSummaries.find((channel) => channel.channel === "slack")?.directMessagesPolicy,
|
|
73878
74150
|
telegramDirectMessagesPolicy: summary.channelSummaries.find((channel) => channel.channel === "telegram")?.directMessagesPolicy,
|
|
73879
74151
|
conditionalOnly: true
|
|
@@ -73896,6 +74168,8 @@ function renderStartSummary(summary) {
|
|
|
73896
74168
|
lines.push(...renderPairingSetupHelpLines("", {
|
|
73897
74169
|
slackEnabled: summary.channelSummaries.some((channel) => channel.channel === "slack" && channel.enabled),
|
|
73898
74170
|
telegramEnabled: summary.channelSummaries.some((channel) => channel.channel === "telegram" && channel.enabled),
|
|
74171
|
+
ownerConfigured: hasConfiguredPrivilegedPrincipal(summary),
|
|
74172
|
+
ownerClaimWindowMinutes: summary.ownerSummary.ownerClaimWindowMinutes,
|
|
73899
74173
|
slackDirectMessagesPolicy: summary.channelSummaries.find((channel) => channel.channel === "slack")?.directMessagesPolicy,
|
|
73900
74174
|
telegramDirectMessagesPolicy: summary.channelSummaries.find((channel) => channel.channel === "telegram")?.directMessagesPolicy,
|
|
73901
74175
|
conditionalOnly: true
|