@sentry/junior 0.82.0 → 0.84.0
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/{agent-hooks-5ZRILRC3.js → agent-hooks-XDS5DIVS.js} +8 -8
- package/dist/api-reference.d.ts +1 -1
- package/dist/app.d.ts +1 -1
- package/dist/app.js +3833 -3263
- package/dist/chat/config.d.ts +1 -1
- package/dist/chat/conversations/store.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +3 -3
- package/dist/chat/resource-events/ingest.d.ts +23 -0
- package/dist/chat/resource-events/notification.d.ts +20 -0
- package/dist/chat/resource-events/store.d.ts +79 -0
- package/dist/chat/services/pending-auth.d.ts +5 -0
- package/dist/chat/slack/client.d.ts +3 -1
- package/dist/chat/state/session-log.d.ts +4 -0
- package/dist/chat/task-execution/slack-work.d.ts +22 -0
- package/dist/chat/task-execution/state.d.ts +8 -2
- package/dist/chat/task-execution/store.d.ts +30 -8
- package/dist/chat/tools/resource-events.d.ts +19 -0
- package/dist/{chunk-PQ2U2AO3.js → chunk-AAD2D7K3.js} +11 -11
- package/dist/{chunk-ABRNFE3N.js → chunk-ALL7GG6U.js} +7 -6
- package/dist/{chunk-4XHCVBXH.js → chunk-BAPRSWNW.js} +81 -13
- package/dist/{chunk-NC6LR6U4.js → chunk-BFPOSIYV.js} +725 -169
- package/dist/{chunk-GUO4EE7L.js → chunk-CWMMGUWM.js} +1 -1
- package/dist/{chunk-YA2JCC7G.js → chunk-JUI6XIHU.js} +13 -5
- package/dist/{chunk-TR2G37II.js → chunk-KNVN4HUT.js} +8 -8
- package/dist/{chunk-X2FL5ZH5.js → chunk-MJ4E2PK7.js} +1 -1
- package/dist/{chunk-3JKTVW4R.js → chunk-MZ4DUVFD.js} +2 -2
- package/dist/{chunk-2AJ4TEKE.js → chunk-S7SC5IUJ.js} +26 -4
- package/dist/{chunk-IOBSRZK5.js → chunk-SBYMRDH7.js} +1 -1
- package/dist/{chunk-LX5GBMEP.js → chunk-ZPD5YQFZ.js} +2 -2
- package/dist/cli/chat.js +4 -4
- package/dist/cli/plugins.js +6 -6
- package/dist/cli/snapshot-warmup.js +3 -3
- package/dist/cli/upgrade.js +16 -32
- package/dist/{db-GQJKBX5W.js → db-7RRWCYF3.js} +3 -3
- package/dist/handlers/github-webhook/check-suite.d.ts +3 -0
- package/dist/handlers/github-webhook/pull-request-review.d.ts +3 -0
- package/dist/handlers/github-webhook/pull-request.d.ts +3 -0
- package/dist/handlers/github-webhook/resource.d.ts +10 -0
- package/dist/handlers/github-webhook.d.ts +15 -0
- package/dist/nitro.js +3 -3
- package/dist/reporting/conversations.d.ts +79 -2
- package/dist/reporting.d.ts +12 -4
- package/dist/reporting.js +431 -27
- package/dist/{runner-VRFJLG2M.js → runner-CJL5IKFO.js} +9 -9
- package/package.json +6 -6
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isSnapshotMissingError,
|
|
11
11
|
resolveRuntimeDependencySnapshot,
|
|
12
12
|
runNonInteractiveCommand
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-MZ4DUVFD.js";
|
|
14
14
|
import {
|
|
15
15
|
pluginTaskId,
|
|
16
16
|
pluginTaskParamsSchema,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
recordSubagentStarted,
|
|
32
32
|
recordToolExecutionStarted,
|
|
33
33
|
upsertAgentTurnSessionRecord
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-JUI6XIHU.js";
|
|
35
35
|
import {
|
|
36
36
|
createPluginEmbedder,
|
|
37
37
|
createPluginHookRunner,
|
|
@@ -42,14 +42,14 @@ import {
|
|
|
42
42
|
getPlugins,
|
|
43
43
|
getSlackToolContext,
|
|
44
44
|
resolveChannelCapabilities
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-KNVN4HUT.js";
|
|
46
46
|
import {
|
|
47
47
|
createPluginLogger,
|
|
48
48
|
createPluginState
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-CWMMGUWM.js";
|
|
50
50
|
import {
|
|
51
51
|
getDb
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-AAD2D7K3.js";
|
|
53
53
|
import {
|
|
54
54
|
SANDBOX_DATA_ROOT,
|
|
55
55
|
SANDBOX_SKILLS_ROOT,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
import {
|
|
61
61
|
acquireActiveLock,
|
|
62
62
|
getStateAdapter
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-MJ4E2PK7.js";
|
|
64
64
|
import {
|
|
65
65
|
SlackActionError,
|
|
66
66
|
downloadPrivateSlackFile,
|
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
normalizeSlackConversationId,
|
|
72
72
|
parseDestination,
|
|
73
73
|
withSlackRetries
|
|
74
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-BAPRSWNW.js";
|
|
75
75
|
import {
|
|
76
76
|
GEN_AI_PROVIDER_NAME,
|
|
77
77
|
GEN_AI_SERVER_ADDRESS,
|
|
@@ -104,7 +104,7 @@ import {
|
|
|
104
104
|
toObservablePromptPart,
|
|
105
105
|
trimTrailingAssistantMessages,
|
|
106
106
|
upsertActiveSkill
|
|
107
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-ALL7GG6U.js";
|
|
108
108
|
import {
|
|
109
109
|
parseSlackThreadId,
|
|
110
110
|
resolveConversationPrivacy,
|
|
@@ -777,8 +777,8 @@ var StateAdapterTokenStore = class {
|
|
|
777
777
|
async set(userId, provider, tokens) {
|
|
778
778
|
const parsed = storedTokensSchema.parse(tokens);
|
|
779
779
|
const expiresAt = parsed.refreshTokenExpiresAt ?? parsed.expiresAt;
|
|
780
|
-
const
|
|
781
|
-
await this.state.set(tokenKey(userId, provider), parsed,
|
|
780
|
+
const ttlMs2 = expiresAt ? Math.max(expiresAt - Date.now() + BUFFER_MS, BUFFER_MS) : LONG_LIVED_TTL_MS;
|
|
781
|
+
await this.state.set(tokenKey(userId, provider), parsed, ttlMs2);
|
|
782
782
|
}
|
|
783
783
|
async delete(userId, provider) {
|
|
784
784
|
await this.state.delete(tokenKey(userId, provider));
|
|
@@ -3770,9 +3770,476 @@ function createReportProgressTool() {
|
|
|
3770
3770
|
});
|
|
3771
3771
|
}
|
|
3772
3772
|
|
|
3773
|
-
// src/chat/tools/
|
|
3773
|
+
// src/chat/tools/resource-events.ts
|
|
3774
3774
|
import { Type as Type13 } from "@sinclair/typebox";
|
|
3775
3775
|
|
|
3776
|
+
// src/chat/resource-events/store.ts
|
|
3777
|
+
import { createHash } from "crypto";
|
|
3778
|
+
import { destinationSchema } from "@sentry/junior-plugin-api";
|
|
3779
|
+
import { z } from "zod";
|
|
3780
|
+
var RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription";
|
|
3781
|
+
var INDEX_LOCK_TTL_MS = 1e4;
|
|
3782
|
+
var SUBSCRIPTION_LOCK_TTL_MS = 1e4;
|
|
3783
|
+
var subscriptionStatusSchema = z.enum(["active", "cancelled", "completed"]);
|
|
3784
|
+
var subscriptionIdIndexSchema = z.array(z.string().min(1));
|
|
3785
|
+
var subscriptionSchema = z.object({
|
|
3786
|
+
conversationId: z.string().min(1),
|
|
3787
|
+
createdAtMs: z.number().finite(),
|
|
3788
|
+
destination: destinationSchema,
|
|
3789
|
+
events: z.array(z.string().min(1)).min(1),
|
|
3790
|
+
expiresAtMs: z.number().finite(),
|
|
3791
|
+
id: z.string().min(1),
|
|
3792
|
+
intent: z.string().min(1),
|
|
3793
|
+
label: z.string().min(1),
|
|
3794
|
+
provider: z.string().min(1),
|
|
3795
|
+
resourceRef: z.string().min(1),
|
|
3796
|
+
resourceType: z.string().min(1),
|
|
3797
|
+
status: subscriptionStatusSchema,
|
|
3798
|
+
updatedAtMs: z.number().finite()
|
|
3799
|
+
}).strict();
|
|
3800
|
+
function digest(value) {
|
|
3801
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 32);
|
|
3802
|
+
}
|
|
3803
|
+
function subscriptionKey(id) {
|
|
3804
|
+
return `${RESOURCE_EVENT_PREFIX}:record:${id}`;
|
|
3805
|
+
}
|
|
3806
|
+
function subscriptionLockKey(id) {
|
|
3807
|
+
return `${RESOURCE_EVENT_PREFIX}:lock:${id}`;
|
|
3808
|
+
}
|
|
3809
|
+
function resourceIndexKey(provider, resourceRef) {
|
|
3810
|
+
return `${RESOURCE_EVENT_PREFIX}:resource:${digest(`${provider}\0${resourceRef}`)}`;
|
|
3811
|
+
}
|
|
3812
|
+
function conversationIndexKey(conversationId) {
|
|
3813
|
+
return `${RESOURCE_EVENT_PREFIX}:conversation:${digest(conversationId)}`;
|
|
3814
|
+
}
|
|
3815
|
+
function indexLockKey(key) {
|
|
3816
|
+
return `${key}:lock`;
|
|
3817
|
+
}
|
|
3818
|
+
function ttlUntil(expiresAtMs, nowMs) {
|
|
3819
|
+
return Math.max(1, expiresAtMs - nowMs);
|
|
3820
|
+
}
|
|
3821
|
+
async function readSubscriptionIdIndex(state, key) {
|
|
3822
|
+
const value = await state.get(key);
|
|
3823
|
+
if (value === void 0 || value === null) {
|
|
3824
|
+
return [];
|
|
3825
|
+
}
|
|
3826
|
+
return subscriptionIdIndexSchema.parse(value);
|
|
3827
|
+
}
|
|
3828
|
+
function buildSubscriptionId(input) {
|
|
3829
|
+
const eventKey = [...new Set(input.events)].sort().join("\0");
|
|
3830
|
+
return `resub_${digest(
|
|
3831
|
+
`${input.provider}\0${input.resourceRef}\0${input.conversationId}\0${eventKey}`
|
|
3832
|
+
)}`;
|
|
3833
|
+
}
|
|
3834
|
+
async function withIndexLock(state, key, callback) {
|
|
3835
|
+
const lock = await state.acquireLock(indexLockKey(key), INDEX_LOCK_TTL_MS);
|
|
3836
|
+
if (!lock) {
|
|
3837
|
+
throw new Error(`Could not acquire resource event index lock for ${key}`);
|
|
3838
|
+
}
|
|
3839
|
+
try {
|
|
3840
|
+
return await callback();
|
|
3841
|
+
} finally {
|
|
3842
|
+
await state.releaseLock(lock);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
async function addToIndex(state, key, subscriptionId, nowMs) {
|
|
3846
|
+
await withIndexLock(state, key, async () => {
|
|
3847
|
+
const ids = [...new Set(await readSubscriptionIdIndex(state, key))];
|
|
3848
|
+
const next = ids.includes(subscriptionId) ? ids : [...ids, subscriptionId];
|
|
3849
|
+
await state.set(key, next, await indexTtlMs(state, next, nowMs));
|
|
3850
|
+
});
|
|
3851
|
+
}
|
|
3852
|
+
async function removeFromIndex(state, key, subscriptionId, nowMs) {
|
|
3853
|
+
await withIndexLock(state, key, async () => {
|
|
3854
|
+
const existing = await readSubscriptionIdIndex(state, key);
|
|
3855
|
+
const next = existing.filter((id) => id !== subscriptionId);
|
|
3856
|
+
await state.set(key, next, await indexTtlMs(state, next, nowMs));
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
async function indexTtlMs(state, subscriptionIds, nowMs) {
|
|
3860
|
+
const records = await Promise.all(
|
|
3861
|
+
subscriptionIds.map(
|
|
3862
|
+
async (id) => parseSubscription(await state.get(subscriptionKey(id)))
|
|
3863
|
+
)
|
|
3864
|
+
);
|
|
3865
|
+
const latestExpiresAtMs = Math.max(
|
|
3866
|
+
nowMs,
|
|
3867
|
+
...records.filter(
|
|
3868
|
+
(record) => record !== void 0 && activeAt(record, nowMs)
|
|
3869
|
+
).map((record) => record.expiresAtMs)
|
|
3870
|
+
);
|
|
3871
|
+
return ttlUntil(latestExpiresAtMs, nowMs);
|
|
3872
|
+
}
|
|
3873
|
+
function parseSubscription(value) {
|
|
3874
|
+
if (value === void 0 || value === null) {
|
|
3875
|
+
return void 0;
|
|
3876
|
+
}
|
|
3877
|
+
return subscriptionSchema.parse(value);
|
|
3878
|
+
}
|
|
3879
|
+
function activeAt(subscription, nowMs) {
|
|
3880
|
+
return subscription.status === "active" && subscription.expiresAtMs > nowMs;
|
|
3881
|
+
}
|
|
3882
|
+
function matchesEvent(subscription, input) {
|
|
3883
|
+
return subscription.provider === input.provider && subscription.resourceRef === input.resourceRef && subscription.events.includes(input.eventType) && activeAt(subscription, input.nowMs);
|
|
3884
|
+
}
|
|
3885
|
+
async function createResourceEventSubscription(input, options = {}) {
|
|
3886
|
+
const state = options.state ?? getStateAdapter();
|
|
3887
|
+
await state.connect();
|
|
3888
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
3889
|
+
const events = [...new Set(input.events.map((event) => event.trim()))].filter(
|
|
3890
|
+
Boolean
|
|
3891
|
+
);
|
|
3892
|
+
if (events.length === 0) {
|
|
3893
|
+
throw new Error("Resource event subscription requires at least one event");
|
|
3894
|
+
}
|
|
3895
|
+
if (input.expiresAtMs <= nowMs) {
|
|
3896
|
+
throw new Error("Resource event subscription expiry must be in the future");
|
|
3897
|
+
}
|
|
3898
|
+
const id = buildSubscriptionId({
|
|
3899
|
+
conversationId: input.conversationId,
|
|
3900
|
+
events,
|
|
3901
|
+
provider: input.provider,
|
|
3902
|
+
resourceRef: input.resourceRef
|
|
3903
|
+
});
|
|
3904
|
+
const record = {
|
|
3905
|
+
conversationId: input.conversationId,
|
|
3906
|
+
createdAtMs: nowMs,
|
|
3907
|
+
destination: input.destination,
|
|
3908
|
+
events,
|
|
3909
|
+
expiresAtMs: input.expiresAtMs,
|
|
3910
|
+
id,
|
|
3911
|
+
intent: input.intent,
|
|
3912
|
+
label: input.label,
|
|
3913
|
+
provider: input.provider,
|
|
3914
|
+
resourceRef: input.resourceRef,
|
|
3915
|
+
resourceType: input.resourceType,
|
|
3916
|
+
status: "active",
|
|
3917
|
+
updatedAtMs: nowMs
|
|
3918
|
+
};
|
|
3919
|
+
const parsed = subscriptionSchema.parse(record);
|
|
3920
|
+
await state.set(
|
|
3921
|
+
subscriptionKey(id),
|
|
3922
|
+
parsed,
|
|
3923
|
+
ttlUntil(parsed.expiresAtMs, nowMs)
|
|
3924
|
+
);
|
|
3925
|
+
await addToIndex(
|
|
3926
|
+
state,
|
|
3927
|
+
resourceIndexKey(input.provider, input.resourceRef),
|
|
3928
|
+
id,
|
|
3929
|
+
nowMs
|
|
3930
|
+
);
|
|
3931
|
+
await addToIndex(
|
|
3932
|
+
state,
|
|
3933
|
+
conversationIndexKey(input.conversationId),
|
|
3934
|
+
id,
|
|
3935
|
+
nowMs
|
|
3936
|
+
);
|
|
3937
|
+
return parsed;
|
|
3938
|
+
}
|
|
3939
|
+
async function listResourceEventSubscriptions(input) {
|
|
3940
|
+
const state = input.state ?? getStateAdapter();
|
|
3941
|
+
await state.connect();
|
|
3942
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
3943
|
+
const ids = await readSubscriptionIdIndex(
|
|
3944
|
+
state,
|
|
3945
|
+
conversationIndexKey(input.conversationId)
|
|
3946
|
+
);
|
|
3947
|
+
const records = await Promise.all(
|
|
3948
|
+
ids.map(
|
|
3949
|
+
async (id) => parseSubscription(await state.get(subscriptionKey(id)))
|
|
3950
|
+
)
|
|
3951
|
+
);
|
|
3952
|
+
return records.filter(
|
|
3953
|
+
(record) => record !== void 0 && record.conversationId === input.conversationId && activeAt(record, nowMs)
|
|
3954
|
+
).sort((left, right) => left.createdAtMs - right.createdAtMs);
|
|
3955
|
+
}
|
|
3956
|
+
async function cancelResourceEventSubscription(input) {
|
|
3957
|
+
const state = input.state ?? getStateAdapter();
|
|
3958
|
+
await state.connect();
|
|
3959
|
+
const lock = await state.acquireLock(
|
|
3960
|
+
subscriptionLockKey(input.id),
|
|
3961
|
+
SUBSCRIPTION_LOCK_TTL_MS
|
|
3962
|
+
);
|
|
3963
|
+
if (!lock) {
|
|
3964
|
+
throw new Error(`Could not acquire subscription lock for ${input.id}`);
|
|
3965
|
+
}
|
|
3966
|
+
try {
|
|
3967
|
+
const current = parseSubscription(
|
|
3968
|
+
await state.get(subscriptionKey(input.id))
|
|
3969
|
+
);
|
|
3970
|
+
if (!current || current.conversationId !== input.conversationId) {
|
|
3971
|
+
return void 0;
|
|
3972
|
+
}
|
|
3973
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
3974
|
+
const next = {
|
|
3975
|
+
...current,
|
|
3976
|
+
status: "cancelled",
|
|
3977
|
+
updatedAtMs: nowMs
|
|
3978
|
+
};
|
|
3979
|
+
await state.set(
|
|
3980
|
+
subscriptionKey(input.id),
|
|
3981
|
+
next,
|
|
3982
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
3983
|
+
);
|
|
3984
|
+
await removeFromIndex(
|
|
3985
|
+
state,
|
|
3986
|
+
resourceIndexKey(current.provider, current.resourceRef),
|
|
3987
|
+
input.id,
|
|
3988
|
+
nowMs
|
|
3989
|
+
);
|
|
3990
|
+
await removeFromIndex(
|
|
3991
|
+
state,
|
|
3992
|
+
conversationIndexKey(current.conversationId),
|
|
3993
|
+
input.id,
|
|
3994
|
+
nowMs
|
|
3995
|
+
);
|
|
3996
|
+
return next;
|
|
3997
|
+
} finally {
|
|
3998
|
+
await state.releaseLock(lock);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
async function findMatchingResourceEventSubscriptions(input) {
|
|
4002
|
+
const state = input.state ?? getStateAdapter();
|
|
4003
|
+
await state.connect();
|
|
4004
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
4005
|
+
const ids = await readSubscriptionIdIndex(
|
|
4006
|
+
state,
|
|
4007
|
+
resourceIndexKey(input.provider, input.resourceRef)
|
|
4008
|
+
);
|
|
4009
|
+
const records = await Promise.all(
|
|
4010
|
+
ids.map(
|
|
4011
|
+
async (id) => parseSubscription(await state.get(subscriptionKey(id)))
|
|
4012
|
+
)
|
|
4013
|
+
);
|
|
4014
|
+
return records.filter(
|
|
4015
|
+
(record) => record !== void 0 && matchesEvent(record, {
|
|
4016
|
+
eventType: input.eventType,
|
|
4017
|
+
nowMs,
|
|
4018
|
+
provider: input.provider,
|
|
4019
|
+
resourceRef: input.resourceRef
|
|
4020
|
+
})
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
async function deliverResourceEventSubscription(input) {
|
|
4024
|
+
const state = input.state ?? getStateAdapter();
|
|
4025
|
+
await state.connect();
|
|
4026
|
+
const lock = await state.acquireLock(
|
|
4027
|
+
subscriptionLockKey(input.subscription.id),
|
|
4028
|
+
SUBSCRIPTION_LOCK_TTL_MS
|
|
4029
|
+
);
|
|
4030
|
+
if (!lock) {
|
|
4031
|
+
throw new Error(
|
|
4032
|
+
`Resource event subscription delivery lock busy: ${input.subscription.id}`
|
|
4033
|
+
);
|
|
4034
|
+
}
|
|
4035
|
+
try {
|
|
4036
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
4037
|
+
const current = parseSubscription(
|
|
4038
|
+
await state.get(subscriptionKey(input.subscription.id))
|
|
4039
|
+
);
|
|
4040
|
+
if (!current || !matchesEvent(current, {
|
|
4041
|
+
eventType: input.eventType,
|
|
4042
|
+
nowMs,
|
|
4043
|
+
provider: input.provider,
|
|
4044
|
+
resourceRef: input.resourceRef
|
|
4045
|
+
})) {
|
|
4046
|
+
return false;
|
|
4047
|
+
}
|
|
4048
|
+
const delivered = await input.deliver(current);
|
|
4049
|
+
if (input.terminal) {
|
|
4050
|
+
const latest = parseSubscription(
|
|
4051
|
+
await state.get(subscriptionKey(current.id))
|
|
4052
|
+
);
|
|
4053
|
+
if (!latest || latest.status !== current.status || latest.updatedAtMs !== current.updatedAtMs) {
|
|
4054
|
+
return delivered;
|
|
4055
|
+
}
|
|
4056
|
+
const next = {
|
|
4057
|
+
...current,
|
|
4058
|
+
status: "completed",
|
|
4059
|
+
updatedAtMs: nowMs
|
|
4060
|
+
};
|
|
4061
|
+
await state.set(
|
|
4062
|
+
subscriptionKey(current.id),
|
|
4063
|
+
next,
|
|
4064
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
4065
|
+
);
|
|
4066
|
+
await removeFromIndex(
|
|
4067
|
+
state,
|
|
4068
|
+
resourceIndexKey(current.provider, current.resourceRef),
|
|
4069
|
+
current.id,
|
|
4070
|
+
nowMs
|
|
4071
|
+
);
|
|
4072
|
+
await removeFromIndex(
|
|
4073
|
+
state,
|
|
4074
|
+
conversationIndexKey(current.conversationId),
|
|
4075
|
+
current.id,
|
|
4076
|
+
nowMs
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
return delivered;
|
|
4080
|
+
} finally {
|
|
4081
|
+
await state.releaseLock(lock);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
// src/chat/tools/resource-events.ts
|
|
4086
|
+
var DEFAULT_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
4087
|
+
var MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
4088
|
+
var subscribeInputSchema = Type13.Object(
|
|
4089
|
+
{
|
|
4090
|
+
resourceRef: Type13.String({
|
|
4091
|
+
description: "Opaque resource ref copied from a subscribable tool result."
|
|
4092
|
+
}),
|
|
4093
|
+
provider: Type13.String({
|
|
4094
|
+
description: "Provider that owns the resource ref."
|
|
4095
|
+
}),
|
|
4096
|
+
resourceType: Type13.String({
|
|
4097
|
+
description: "Provider-defined resource type from the subscribable hint."
|
|
4098
|
+
}),
|
|
4099
|
+
label: Type13.String({
|
|
4100
|
+
description: "Human-readable resource label from the subscribable hint."
|
|
4101
|
+
}),
|
|
4102
|
+
events: Type13.Array(Type13.String(), {
|
|
4103
|
+
description: "High-signal event names to deliver to this conversation when they occur.",
|
|
4104
|
+
minItems: 1
|
|
4105
|
+
}),
|
|
4106
|
+
intent: Type13.String({
|
|
4107
|
+
description: "Concise reason this conversation wants these events, used when an event arrives."
|
|
4108
|
+
}),
|
|
4109
|
+
ttlMs: Type13.Optional(
|
|
4110
|
+
Type13.Number({
|
|
4111
|
+
description: "How long to keep the subscription active. Defaults to 14 days and is capped at 30 days."
|
|
4112
|
+
})
|
|
4113
|
+
)
|
|
4114
|
+
},
|
|
4115
|
+
{ additionalProperties: false }
|
|
4116
|
+
);
|
|
4117
|
+
var cancelInputSchema = Type13.Object(
|
|
4118
|
+
{
|
|
4119
|
+
subscriptionId: Type13.String({
|
|
4120
|
+
description: "Subscription id returned by subscribeToResourceEvents or listResourceEventSubscriptions."
|
|
4121
|
+
})
|
|
4122
|
+
},
|
|
4123
|
+
{ additionalProperties: false }
|
|
4124
|
+
);
|
|
4125
|
+
function requireConversationContext(context) {
|
|
4126
|
+
if (!context.conversationId) {
|
|
4127
|
+
throw new Error("Resource event subscriptions require a conversation");
|
|
4128
|
+
}
|
|
4129
|
+
if (context.destination.platform !== "slack") {
|
|
4130
|
+
throw new Error(
|
|
4131
|
+
"Resource event subscriptions currently require Slack delivery"
|
|
4132
|
+
);
|
|
4133
|
+
}
|
|
4134
|
+
if (!isSlackThreadConversationId(context.conversationId)) {
|
|
4135
|
+
throw new Error(
|
|
4136
|
+
"Resource event subscriptions require a Slack thread conversation"
|
|
4137
|
+
);
|
|
4138
|
+
}
|
|
4139
|
+
return context.conversationId;
|
|
4140
|
+
}
|
|
4141
|
+
function canUseResourceEventSubscriptionTools(context) {
|
|
4142
|
+
return context.destination.platform === "slack" && Boolean(
|
|
4143
|
+
context.conversationId && isSlackThreadConversationId(context.conversationId)
|
|
4144
|
+
);
|
|
4145
|
+
}
|
|
4146
|
+
function isSlackThreadConversationId(conversationId) {
|
|
4147
|
+
const parts = conversationId.split(":");
|
|
4148
|
+
return parts.length === 3 && parts[0] === "slack" && Boolean(parts[1]) && Boolean(parts[2]);
|
|
4149
|
+
}
|
|
4150
|
+
function cleanStrings(values) {
|
|
4151
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
4152
|
+
}
|
|
4153
|
+
function ttlMs(input) {
|
|
4154
|
+
if (input.ttlMs === void 0) {
|
|
4155
|
+
return DEFAULT_TTL_MS;
|
|
4156
|
+
}
|
|
4157
|
+
if (!Number.isFinite(input.ttlMs) || input.ttlMs <= 0) {
|
|
4158
|
+
throw new Error("ttlMs must be a positive finite number");
|
|
4159
|
+
}
|
|
4160
|
+
return Math.min(input.ttlMs, MAX_TTL_MS);
|
|
4161
|
+
}
|
|
4162
|
+
function createSubscribeToResourceEventsTool(context) {
|
|
4163
|
+
return tool({
|
|
4164
|
+
description: "Subscribe the current conversation to high-signal events for a resource returned by a subscribable tool result. Matching events are queued as normal conversation messages; they do not interrupt active work.",
|
|
4165
|
+
inputSchema: subscribeInputSchema,
|
|
4166
|
+
async execute(input) {
|
|
4167
|
+
const conversationId = requireConversationContext(context);
|
|
4168
|
+
const events = cleanStrings(input.events);
|
|
4169
|
+
const intent = input.intent.trim();
|
|
4170
|
+
if (!intent) {
|
|
4171
|
+
throw new Error("intent is required");
|
|
4172
|
+
}
|
|
4173
|
+
const nowMs = Date.now();
|
|
4174
|
+
const subscription = await createResourceEventSubscription({
|
|
4175
|
+
conversationId,
|
|
4176
|
+
destination: context.destination,
|
|
4177
|
+
events,
|
|
4178
|
+
expiresAtMs: nowMs + ttlMs(input),
|
|
4179
|
+
intent,
|
|
4180
|
+
label: input.label.trim(),
|
|
4181
|
+
provider: input.provider.trim(),
|
|
4182
|
+
resourceRef: input.resourceRef.trim(),
|
|
4183
|
+
resourceType: input.resourceType.trim()
|
|
4184
|
+
});
|
|
4185
|
+
return {
|
|
4186
|
+
id: subscription.id,
|
|
4187
|
+
status: subscription.status,
|
|
4188
|
+
resourceRef: subscription.resourceRef,
|
|
4189
|
+
events: subscription.events,
|
|
4190
|
+
expiresAtMs: subscription.expiresAtMs
|
|
4191
|
+
};
|
|
4192
|
+
}
|
|
4193
|
+
});
|
|
4194
|
+
}
|
|
4195
|
+
function createListResourceEventSubscriptionsTool(context) {
|
|
4196
|
+
return tool({
|
|
4197
|
+
description: "List active resource event subscriptions for the current conversation.",
|
|
4198
|
+
inputSchema: Type13.Object({}, { additionalProperties: false }),
|
|
4199
|
+
async execute() {
|
|
4200
|
+
const conversationId = requireConversationContext(context);
|
|
4201
|
+
const subscriptions = await listResourceEventSubscriptions({
|
|
4202
|
+
conversationId
|
|
4203
|
+
});
|
|
4204
|
+
return {
|
|
4205
|
+
subscriptions: subscriptions.map((subscription) => ({
|
|
4206
|
+
id: subscription.id,
|
|
4207
|
+
label: subscription.label,
|
|
4208
|
+
resourceRef: subscription.resourceRef,
|
|
4209
|
+
provider: subscription.provider,
|
|
4210
|
+
resourceType: subscription.resourceType,
|
|
4211
|
+
events: subscription.events,
|
|
4212
|
+
intent: subscription.intent,
|
|
4213
|
+
expiresAtMs: subscription.expiresAtMs
|
|
4214
|
+
}))
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
});
|
|
4218
|
+
}
|
|
4219
|
+
function createCancelResourceEventSubscriptionTool(context) {
|
|
4220
|
+
return tool({
|
|
4221
|
+
description: "Cancel a resource event subscription for the current conversation.",
|
|
4222
|
+
inputSchema: cancelInputSchema,
|
|
4223
|
+
async execute(input) {
|
|
4224
|
+
const conversationId = requireConversationContext(context);
|
|
4225
|
+
const subscription = await cancelResourceEventSubscription({
|
|
4226
|
+
conversationId,
|
|
4227
|
+
id: input.subscriptionId
|
|
4228
|
+
});
|
|
4229
|
+
if (!subscription) {
|
|
4230
|
+
throw new Error("Resource event subscription was not found");
|
|
4231
|
+
}
|
|
4232
|
+
return {
|
|
4233
|
+
id: subscription.id,
|
|
4234
|
+
status: subscription.status
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
// src/chat/tools/slack/channel-list-messages.ts
|
|
4241
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
4242
|
+
|
|
3776
4243
|
// src/chat/slack/channel.ts
|
|
3777
4244
|
async function listChannelMessages(input) {
|
|
3778
4245
|
const client = getSlackClient();
|
|
@@ -3862,39 +4329,39 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
3862
4329
|
return tool({
|
|
3863
4330
|
description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
|
|
3864
4331
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
3865
|
-
inputSchema:
|
|
3866
|
-
limit:
|
|
3867
|
-
|
|
4332
|
+
inputSchema: Type14.Object({
|
|
4333
|
+
limit: Type14.Optional(
|
|
4334
|
+
Type14.Integer({
|
|
3868
4335
|
minimum: 1,
|
|
3869
4336
|
maximum: 1e3,
|
|
3870
4337
|
description: "Maximum number of messages to return across pages."
|
|
3871
4338
|
})
|
|
3872
4339
|
),
|
|
3873
|
-
cursor:
|
|
3874
|
-
|
|
4340
|
+
cursor: Type14.Optional(
|
|
4341
|
+
Type14.String({
|
|
3875
4342
|
minLength: 1,
|
|
3876
4343
|
description: "Optional cursor to continue from a prior call."
|
|
3877
4344
|
})
|
|
3878
4345
|
),
|
|
3879
|
-
oldest:
|
|
3880
|
-
|
|
4346
|
+
oldest: Type14.Optional(
|
|
4347
|
+
Type14.String({
|
|
3881
4348
|
minLength: 1,
|
|
3882
4349
|
description: "Optional oldest message timestamp (Slack ts) for range filtering."
|
|
3883
4350
|
})
|
|
3884
4351
|
),
|
|
3885
|
-
latest:
|
|
3886
|
-
|
|
4352
|
+
latest: Type14.Optional(
|
|
4353
|
+
Type14.String({
|
|
3887
4354
|
minLength: 1,
|
|
3888
4355
|
description: "Optional latest message timestamp (Slack ts) for range filtering."
|
|
3889
4356
|
})
|
|
3890
4357
|
),
|
|
3891
|
-
inclusive:
|
|
3892
|
-
|
|
4358
|
+
inclusive: Type14.Optional(
|
|
4359
|
+
Type14.Boolean({
|
|
3893
4360
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
3894
4361
|
})
|
|
3895
4362
|
),
|
|
3896
|
-
max_pages:
|
|
3897
|
-
|
|
4363
|
+
max_pages: Type14.Optional(
|
|
4364
|
+
Type14.Integer({
|
|
3898
4365
|
minimum: 1,
|
|
3899
4366
|
maximum: 10,
|
|
3900
4367
|
description: "Maximum number of API pages to traverse in a single call."
|
|
@@ -3954,7 +4421,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
3954
4421
|
}
|
|
3955
4422
|
|
|
3956
4423
|
// src/chat/tools/slack/channel-post-message.ts
|
|
3957
|
-
import { Type as
|
|
4424
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
3958
4425
|
|
|
3959
4426
|
// src/chat/slack/outbound.ts
|
|
3960
4427
|
var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
|
|
@@ -3998,7 +4465,13 @@ async function getPermalinkBestEffort(args) {
|
|
|
3998
4465
|
message_ts: args.messageTs
|
|
3999
4466
|
}),
|
|
4000
4467
|
3,
|
|
4001
|
-
{
|
|
4468
|
+
{
|
|
4469
|
+
action: "chat.getPermalink",
|
|
4470
|
+
spanAttributes: {
|
|
4471
|
+
"app.slack.channel_id": args.channelId,
|
|
4472
|
+
"app.slack.message_ts": args.messageTs
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4002
4475
|
);
|
|
4003
4476
|
return response.permalink;
|
|
4004
4477
|
} catch {
|
|
@@ -4025,7 +4498,13 @@ async function postSlackMessage(input) {
|
|
|
4025
4498
|
...threadTs ? { thread_ts: threadTs } : {}
|
|
4026
4499
|
}),
|
|
4027
4500
|
3,
|
|
4028
|
-
{
|
|
4501
|
+
{
|
|
4502
|
+
action: "chat.postMessage",
|
|
4503
|
+
spanAttributes: {
|
|
4504
|
+
"app.slack.channel_id": channelId,
|
|
4505
|
+
...threadTs ? { "app.slack.thread_ts": threadTs } : {}
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4029
4508
|
);
|
|
4030
4509
|
if (!response.ts) {
|
|
4031
4510
|
throw new Error("Slack message posted without ts");
|
|
@@ -4055,7 +4534,13 @@ async function deleteSlackMessage(input) {
|
|
|
4055
4534
|
ts: timestamp
|
|
4056
4535
|
}),
|
|
4057
4536
|
3,
|
|
4058
|
-
{
|
|
4537
|
+
{
|
|
4538
|
+
action: "chat.delete",
|
|
4539
|
+
spanAttributes: {
|
|
4540
|
+
"app.slack.channel_id": channelId,
|
|
4541
|
+
"app.slack.message_ts": timestamp
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4059
4544
|
);
|
|
4060
4545
|
}
|
|
4061
4546
|
async function postSlackEphemeralMessage(input) {
|
|
@@ -4083,7 +4568,14 @@ async function postSlackEphemeralMessage(input) {
|
|
|
4083
4568
|
...threadTs ? { thread_ts: threadTs } : {}
|
|
4084
4569
|
}),
|
|
4085
4570
|
3,
|
|
4086
|
-
{
|
|
4571
|
+
{
|
|
4572
|
+
action: "chat.postEphemeral",
|
|
4573
|
+
spanAttributes: {
|
|
4574
|
+
"app.slack.channel_id": channelId,
|
|
4575
|
+
"app.slack.user_id": userId,
|
|
4576
|
+
...threadTs ? { "app.slack.thread_ts": threadTs } : {}
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4087
4579
|
);
|
|
4088
4580
|
return {
|
|
4089
4581
|
messageTs: response.message_ts
|
|
@@ -4120,7 +4612,13 @@ async function uploadFilesToThread(input) {
|
|
|
4120
4612
|
file_uploads: fileUploads
|
|
4121
4613
|
}),
|
|
4122
4614
|
3,
|
|
4123
|
-
{
|
|
4615
|
+
{
|
|
4616
|
+
action: "filesUploadV2",
|
|
4617
|
+
spanAttributes: {
|
|
4618
|
+
"app.slack.channel_id": channelId,
|
|
4619
|
+
"app.slack.thread_ts": threadTs
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4124
4622
|
);
|
|
4125
4623
|
}
|
|
4126
4624
|
async function addReactionToMessage(input) {
|
|
@@ -4144,7 +4642,14 @@ async function addReactionToMessage(input) {
|
|
|
4144
4642
|
name: emoji
|
|
4145
4643
|
}),
|
|
4146
4644
|
3,
|
|
4147
|
-
{
|
|
4645
|
+
{
|
|
4646
|
+
action: "reactions.add",
|
|
4647
|
+
spanAttributes: {
|
|
4648
|
+
"app.slack.channel_id": channelId,
|
|
4649
|
+
"app.slack.message_ts": timestamp,
|
|
4650
|
+
"app.slack.reaction": emoji
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4148
4653
|
);
|
|
4149
4654
|
} catch (error) {
|
|
4150
4655
|
if (error instanceof SlackActionError && error.code === "already_reacted") {
|
|
@@ -4175,7 +4680,14 @@ async function removeReactionFromMessage(input) {
|
|
|
4175
4680
|
name: emoji
|
|
4176
4681
|
}),
|
|
4177
4682
|
3,
|
|
4178
|
-
{
|
|
4683
|
+
{
|
|
4684
|
+
action: "reactions.remove",
|
|
4685
|
+
spanAttributes: {
|
|
4686
|
+
"app.slack.channel_id": channelId,
|
|
4687
|
+
"app.slack.message_ts": timestamp,
|
|
4688
|
+
"app.slack.reaction": emoji
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4179
4691
|
);
|
|
4180
4692
|
} catch (error) {
|
|
4181
4693
|
if (error instanceof SlackActionError && error.code === "no_reaction") {
|
|
@@ -4206,8 +4718,8 @@ function createOperationKey(toolName, input) {
|
|
|
4206
4718
|
function createSlackChannelPostMessageTool(context, state) {
|
|
4207
4719
|
return tool({
|
|
4208
4720
|
description: "Post a new top-level message to the current Slack channel. Use only when the user explicitly asks to post/send/share/say something to the channel. Do not use for thread replies, inline @mentions, or pinging mentioned users.",
|
|
4209
|
-
inputSchema:
|
|
4210
|
-
text:
|
|
4721
|
+
inputSchema: Type15.Object({
|
|
4722
|
+
text: Type15.String({
|
|
4211
4723
|
minLength: 1,
|
|
4212
4724
|
maxLength: 4e4,
|
|
4213
4725
|
description: "Slack mrkdwn text to post."
|
|
@@ -4247,12 +4759,12 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
4247
4759
|
}
|
|
4248
4760
|
|
|
4249
4761
|
// src/chat/tools/slack/message-add-reaction.ts
|
|
4250
|
-
import { Type as
|
|
4762
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
4251
4763
|
function createSlackMessageAddReactionTool(context, state) {
|
|
4252
4764
|
return tool({
|
|
4253
4765
|
description: "Add an emoji reaction to the current inbound Slack message. Use sparingly for lightweight acknowledgements. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
|
|
4254
|
-
inputSchema:
|
|
4255
|
-
emoji:
|
|
4766
|
+
inputSchema: Type16.Object({
|
|
4767
|
+
emoji: Type16.String({
|
|
4256
4768
|
minLength: 1,
|
|
4257
4769
|
maxLength: 64,
|
|
4258
4770
|
description: "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
|
|
@@ -4304,7 +4816,7 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
4304
4816
|
}
|
|
4305
4817
|
|
|
4306
4818
|
// src/chat/tools/slack/canvas-tools.ts
|
|
4307
|
-
import { Type as
|
|
4819
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
4308
4820
|
|
|
4309
4821
|
// src/chat/slack/canvas-references.ts
|
|
4310
4822
|
var SLACK_FILE_ID_PATTERN = /^F[A-Z0-9]{4,}$/i;
|
|
@@ -4549,13 +5061,13 @@ function resolveCanvasTarget(canvas) {
|
|
|
4549
5061
|
}
|
|
4550
5062
|
return { ok: true, canvasId };
|
|
4551
5063
|
}
|
|
4552
|
-
var editReplacementSchema2 =
|
|
5064
|
+
var editReplacementSchema2 = Type17.Object(
|
|
4553
5065
|
{
|
|
4554
|
-
oldText:
|
|
5066
|
+
oldText: Type17.String({
|
|
4555
5067
|
minLength: 1,
|
|
4556
5068
|
description: "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
|
|
4557
5069
|
}),
|
|
4558
|
-
newText:
|
|
5070
|
+
newText: Type17.String({
|
|
4559
5071
|
description: "Replacement Canvas markdown for this edit."
|
|
4560
5072
|
})
|
|
4561
5073
|
},
|
|
@@ -4564,13 +5076,13 @@ var editReplacementSchema2 = Type16.Object(
|
|
|
4564
5076
|
function createSlackCanvasCreateTool(context, state) {
|
|
4565
5077
|
return tool({
|
|
4566
5078
|
description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
|
|
4567
|
-
inputSchema:
|
|
4568
|
-
title:
|
|
5079
|
+
inputSchema: Type17.Object({
|
|
5080
|
+
title: Type17.String({
|
|
4569
5081
|
minLength: 1,
|
|
4570
5082
|
maxLength: 160,
|
|
4571
5083
|
description: "Canvas title."
|
|
4572
5084
|
}),
|
|
4573
|
-
markdown:
|
|
5085
|
+
markdown: Type17.String({
|
|
4574
5086
|
minLength: 1,
|
|
4575
5087
|
description: "Canvas markdown body content."
|
|
4576
5088
|
})
|
|
@@ -4636,20 +5148,20 @@ function createSlackCanvasReadTool() {
|
|
|
4636
5148
|
return tool({
|
|
4637
5149
|
description: "Read a bounded line range from a Slack canvas as markdown. Use when you need exact Canvas contents to verify facts or make edits safely. Do not use for generic web pages \u2014 use webFetch for those.",
|
|
4638
5150
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
4639
|
-
inputSchema:
|
|
5151
|
+
inputSchema: Type17.Object(
|
|
4640
5152
|
{
|
|
4641
|
-
canvas:
|
|
5153
|
+
canvas: Type17.String({
|
|
4642
5154
|
minLength: 1,
|
|
4643
5155
|
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
4644
5156
|
}),
|
|
4645
|
-
offset:
|
|
4646
|
-
|
|
5157
|
+
offset: Type17.Optional(
|
|
5158
|
+
Type17.Integer({
|
|
4647
5159
|
minimum: 1,
|
|
4648
5160
|
description: "1-indexed line number to start reading from."
|
|
4649
5161
|
})
|
|
4650
5162
|
),
|
|
4651
|
-
limit:
|
|
4652
|
-
|
|
5163
|
+
limit: Type17.Optional(
|
|
5164
|
+
Type17.Integer({
|
|
4653
5165
|
minimum: 1,
|
|
4654
5166
|
description: "Maximum number of lines to read. Defaults to 1000."
|
|
4655
5167
|
})
|
|
@@ -4710,13 +5222,13 @@ function createSlackCanvasEditTool(state) {
|
|
|
4710
5222
|
description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content; prefer this over slackCanvasWrite for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same canvas: use one edits[] call.",
|
|
4711
5223
|
prepareArguments: prepareCanvasEditArguments,
|
|
4712
5224
|
executionMode: "sequential",
|
|
4713
|
-
inputSchema:
|
|
5225
|
+
inputSchema: Type17.Object(
|
|
4714
5226
|
{
|
|
4715
|
-
canvas:
|
|
5227
|
+
canvas: Type17.String({
|
|
4716
5228
|
minLength: 1,
|
|
4717
5229
|
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."
|
|
4718
5230
|
}),
|
|
4719
|
-
edits:
|
|
5231
|
+
edits: Type17.Array(editReplacementSchema2, {
|
|
4720
5232
|
minItems: 1,
|
|
4721
5233
|
description: "Exact replacements matched against the current Canvas body, not incrementally."
|
|
4722
5234
|
})
|
|
@@ -4796,13 +5308,13 @@ function createSlackCanvasWriteTool(state) {
|
|
|
4796
5308
|
return tool({
|
|
4797
5309
|
description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation; use slackCanvasEdit for targeted changes to existing canvas content.",
|
|
4798
5310
|
executionMode: "sequential",
|
|
4799
|
-
inputSchema:
|
|
5311
|
+
inputSchema: Type17.Object(
|
|
4800
5312
|
{
|
|
4801
|
-
canvas:
|
|
5313
|
+
canvas: Type17.String({
|
|
4802
5314
|
minLength: 1,
|
|
4803
5315
|
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."
|
|
4804
5316
|
}),
|
|
4805
|
-
content:
|
|
5317
|
+
content: Type17.String({
|
|
4806
5318
|
description: "UTF-8 markdown content to write."
|
|
4807
5319
|
})
|
|
4808
5320
|
},
|
|
@@ -4863,7 +5375,7 @@ function createSlackCanvasWriteTool(state) {
|
|
|
4863
5375
|
}
|
|
4864
5376
|
|
|
4865
5377
|
// src/chat/tools/slack/list-tools.ts
|
|
4866
|
-
import { Type as
|
|
5378
|
+
import { Type as Type18 } from "@sinclair/typebox";
|
|
4867
5379
|
|
|
4868
5380
|
// src/chat/tools/slack/lists.ts
|
|
4869
5381
|
function normalizeKey(value) {
|
|
@@ -4919,7 +5431,9 @@ async function createTodoList(name) {
|
|
|
4919
5431
|
name,
|
|
4920
5432
|
schema: DEFAULT_TODO_SCHEMA,
|
|
4921
5433
|
todo_mode: true
|
|
4922
|
-
})
|
|
5434
|
+
}),
|
|
5435
|
+
3,
|
|
5436
|
+
{ action: "slackLists.create" }
|
|
4923
5437
|
);
|
|
4924
5438
|
if (!result.list_id) {
|
|
4925
5439
|
throw new Error("Slack list was created without list_id");
|
|
@@ -4965,7 +5479,9 @@ async function addListItems(input) {
|
|
|
4965
5479
|
() => client.slackLists.items.create({
|
|
4966
5480
|
list_id: input.listId,
|
|
4967
5481
|
initial_fields: initialFields
|
|
4968
|
-
})
|
|
5482
|
+
}),
|
|
5483
|
+
3,
|
|
5484
|
+
{ action: "slackLists.items.create" }
|
|
4969
5485
|
);
|
|
4970
5486
|
if (response.item?.id) {
|
|
4971
5487
|
createdItemIds.push(response.item.id);
|
|
@@ -4987,7 +5503,9 @@ async function listItems(listId, limit = 100) {
|
|
|
4987
5503
|
list_id: listId,
|
|
4988
5504
|
limit: cappedLimit,
|
|
4989
5505
|
cursor
|
|
4990
|
-
})
|
|
5506
|
+
}),
|
|
5507
|
+
3,
|
|
5508
|
+
{ action: "slackLists.items.list" }
|
|
4991
5509
|
);
|
|
4992
5510
|
const remaining = cappedLimit - items.length;
|
|
4993
5511
|
if (remaining <= 0) {
|
|
@@ -5029,7 +5547,9 @@ async function updateListItem(input) {
|
|
|
5029
5547
|
() => client.slackLists.items.update({
|
|
5030
5548
|
list_id: input.listId,
|
|
5031
5549
|
cells
|
|
5032
|
-
})
|
|
5550
|
+
}),
|
|
5551
|
+
3,
|
|
5552
|
+
{ action: "slackLists.items.update" }
|
|
5033
5553
|
);
|
|
5034
5554
|
}
|
|
5035
5555
|
|
|
@@ -5037,8 +5557,8 @@ async function updateListItem(input) {
|
|
|
5037
5557
|
function createSlackListCreateTool(state) {
|
|
5038
5558
|
return tool({
|
|
5039
5559
|
description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
|
|
5040
|
-
inputSchema:
|
|
5041
|
-
name:
|
|
5560
|
+
inputSchema: Type18.Object({
|
|
5561
|
+
name: Type18.String({
|
|
5042
5562
|
minLength: 1,
|
|
5043
5563
|
maxLength: 160,
|
|
5044
5564
|
description: "Name for the new Slack list."
|
|
@@ -5073,20 +5593,20 @@ function createSlackListCreateTool(state) {
|
|
|
5073
5593
|
function createSlackListAddItemsTool(state) {
|
|
5074
5594
|
return tool({
|
|
5075
5595
|
description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
|
|
5076
|
-
inputSchema:
|
|
5077
|
-
items:
|
|
5596
|
+
inputSchema: Type18.Object({
|
|
5597
|
+
items: Type18.Array(Type18.String({ minLength: 1 }), {
|
|
5078
5598
|
minItems: 1,
|
|
5079
5599
|
maxItems: 25,
|
|
5080
5600
|
description: "List item titles to create."
|
|
5081
5601
|
}),
|
|
5082
|
-
assignee_user_id:
|
|
5083
|
-
|
|
5602
|
+
assignee_user_id: Type18.Optional(
|
|
5603
|
+
Type18.String({
|
|
5084
5604
|
minLength: 1,
|
|
5085
5605
|
description: "Optional Slack user ID assigned to all created items."
|
|
5086
5606
|
})
|
|
5087
5607
|
),
|
|
5088
|
-
due_date:
|
|
5089
|
-
|
|
5608
|
+
due_date: Type18.Optional(
|
|
5609
|
+
Type18.String({
|
|
5090
5610
|
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
5091
5611
|
description: "Optional due date in YYYY-MM-DD format."
|
|
5092
5612
|
})
|
|
@@ -5136,9 +5656,9 @@ function createSlackListGetItemsTool(state) {
|
|
|
5136
5656
|
return tool({
|
|
5137
5657
|
description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
|
|
5138
5658
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5139
|
-
inputSchema:
|
|
5140
|
-
limit:
|
|
5141
|
-
|
|
5659
|
+
inputSchema: Type18.Object({
|
|
5660
|
+
limit: Type18.Optional(
|
|
5661
|
+
Type18.Integer({
|
|
5142
5662
|
minimum: 1,
|
|
5143
5663
|
maximum: 200,
|
|
5144
5664
|
description: "Maximum number of list items to return."
|
|
@@ -5163,19 +5683,19 @@ function createSlackListGetItemsTool(state) {
|
|
|
5163
5683
|
function createSlackListUpdateItemTool(state) {
|
|
5164
5684
|
return tool({
|
|
5165
5685
|
description: "Update an item in the active Slack list tracked in artifact context (title/completion). Use when the user asks to mark progress or rename a tracked task. Do not use to add new tasks.",
|
|
5166
|
-
inputSchema:
|
|
5686
|
+
inputSchema: Type18.Object(
|
|
5167
5687
|
{
|
|
5168
|
-
item_id:
|
|
5688
|
+
item_id: Type18.String({
|
|
5169
5689
|
minLength: 1,
|
|
5170
5690
|
description: "ID of the Slack list item to update."
|
|
5171
5691
|
}),
|
|
5172
|
-
completed:
|
|
5173
|
-
|
|
5692
|
+
completed: Type18.Optional(
|
|
5693
|
+
Type18.Boolean({
|
|
5174
5694
|
description: "Optional completion status update."
|
|
5175
5695
|
})
|
|
5176
5696
|
),
|
|
5177
|
-
title:
|
|
5178
|
-
|
|
5697
|
+
title: Type18.Optional(
|
|
5698
|
+
Type18.String({
|
|
5179
5699
|
minLength: 1,
|
|
5180
5700
|
description: "Optional new item title."
|
|
5181
5701
|
})
|
|
@@ -5225,7 +5745,7 @@ function createSlackListUpdateItemTool(state) {
|
|
|
5225
5745
|
}
|
|
5226
5746
|
|
|
5227
5747
|
// src/chat/tools/slack/thread-read.ts
|
|
5228
|
-
import { Type as
|
|
5748
|
+
import { Type as Type19 } from "@sinclair/typebox";
|
|
5229
5749
|
|
|
5230
5750
|
// src/chat/tools/slack/slack-message-url.ts
|
|
5231
5751
|
var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
|
|
@@ -5407,34 +5927,34 @@ function createSlackThreadReadTool(context) {
|
|
|
5407
5927
|
return tool({
|
|
5408
5928
|
description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Public channel links can be read if the bot has access; private channels and DMs are only readable when they are the current conversation.",
|
|
5409
5929
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5410
|
-
inputSchema:
|
|
5411
|
-
url:
|
|
5412
|
-
|
|
5930
|
+
inputSchema: Type19.Object({
|
|
5931
|
+
url: Type19.Optional(
|
|
5932
|
+
Type19.String({
|
|
5413
5933
|
minLength: 1,
|
|
5414
5934
|
description: "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
|
|
5415
5935
|
})
|
|
5416
5936
|
),
|
|
5417
|
-
channel_id:
|
|
5418
|
-
|
|
5937
|
+
channel_id: Type19.Optional(
|
|
5938
|
+
Type19.String({
|
|
5419
5939
|
minLength: 1,
|
|
5420
5940
|
description: "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
|
|
5421
5941
|
})
|
|
5422
5942
|
),
|
|
5423
|
-
ts:
|
|
5424
|
-
|
|
5943
|
+
ts: Type19.Optional(
|
|
5944
|
+
Type19.String({
|
|
5425
5945
|
minLength: 1,
|
|
5426
5946
|
description: "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
|
|
5427
5947
|
})
|
|
5428
5948
|
),
|
|
5429
|
-
limit:
|
|
5430
|
-
|
|
5949
|
+
limit: Type19.Optional(
|
|
5950
|
+
Type19.Integer({
|
|
5431
5951
|
minimum: 1,
|
|
5432
5952
|
maximum: 1e3,
|
|
5433
5953
|
description: "Maximum number of thread messages to fetch."
|
|
5434
5954
|
})
|
|
5435
5955
|
),
|
|
5436
|
-
max_pages:
|
|
5437
|
-
|
|
5956
|
+
max_pages: Type19.Optional(
|
|
5957
|
+
Type19.Integer({
|
|
5438
5958
|
minimum: 1,
|
|
5439
5959
|
maximum: 10,
|
|
5440
5960
|
description: "Maximum number of Slack API pages to traverse."
|
|
@@ -5529,7 +6049,7 @@ function createSlackThreadReadTool(context) {
|
|
|
5529
6049
|
}
|
|
5530
6050
|
|
|
5531
6051
|
// src/chat/tools/slack/user-lookup.ts
|
|
5532
|
-
import { Type as
|
|
6052
|
+
import { Type as Type20 } from "@sinclair/typebox";
|
|
5533
6053
|
|
|
5534
6054
|
// src/chat/slack/users.ts
|
|
5535
6055
|
function normalizeUser(raw) {
|
|
@@ -5675,41 +6195,41 @@ function createSlackUserLookupTool() {
|
|
|
5675
6195
|
return tool({
|
|
5676
6196
|
description: "Look up Slack user profiles by user ID, email, or name search. Use when you need to identify a user, resolve cross-platform identity, or look up profile details like title or status. Returns profile fields including custom fields. For user ID lookup, pass a Slack user ID (e.g. U039RR91S). For search, pass a name query.",
|
|
5677
6197
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5678
|
-
inputSchema:
|
|
5679
|
-
user_id:
|
|
5680
|
-
|
|
6198
|
+
inputSchema: Type20.Object({
|
|
6199
|
+
user_id: Type20.Optional(
|
|
6200
|
+
Type20.String({
|
|
5681
6201
|
minLength: 1,
|
|
5682
6202
|
description: "Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
|
|
5683
6203
|
})
|
|
5684
6204
|
),
|
|
5685
|
-
email:
|
|
5686
|
-
|
|
6205
|
+
email: Type20.Optional(
|
|
6206
|
+
Type20.String({
|
|
5687
6207
|
minLength: 3,
|
|
5688
6208
|
description: "Email address to look up. Mutually exclusive with user_id and query."
|
|
5689
6209
|
})
|
|
5690
6210
|
),
|
|
5691
|
-
query:
|
|
5692
|
-
|
|
6211
|
+
query: Type20.Optional(
|
|
6212
|
+
Type20.String({
|
|
5693
6213
|
minLength: 2,
|
|
5694
6214
|
description: "Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
|
|
5695
6215
|
})
|
|
5696
6216
|
),
|
|
5697
|
-
limit:
|
|
5698
|
-
|
|
6217
|
+
limit: Type20.Optional(
|
|
6218
|
+
Type20.Integer({
|
|
5699
6219
|
minimum: 1,
|
|
5700
6220
|
maximum: 20,
|
|
5701
6221
|
description: "Maximum number of results to return for name search. Defaults to 10."
|
|
5702
6222
|
})
|
|
5703
6223
|
),
|
|
5704
|
-
max_pages:
|
|
5705
|
-
|
|
6224
|
+
max_pages: Type20.Optional(
|
|
6225
|
+
Type20.Integer({
|
|
5706
6226
|
minimum: 1,
|
|
5707
6227
|
maximum: 5,
|
|
5708
6228
|
description: "Maximum number of Slack API pages to scan for name search. Defaults to 3."
|
|
5709
6229
|
})
|
|
5710
6230
|
),
|
|
5711
|
-
include_bots:
|
|
5712
|
-
|
|
6231
|
+
include_bots: Type20.Optional(
|
|
6232
|
+
Type20.Boolean({
|
|
5713
6233
|
description: "Include bot accounts in name search results. Defaults to false."
|
|
5714
6234
|
})
|
|
5715
6235
|
)
|
|
@@ -5788,12 +6308,12 @@ function createSlackUserLookupTool() {
|
|
|
5788
6308
|
}
|
|
5789
6309
|
|
|
5790
6310
|
// src/chat/tools/system-time.ts
|
|
5791
|
-
import { Type as
|
|
6311
|
+
import { Type as Type21 } from "@sinclair/typebox";
|
|
5792
6312
|
function createSystemTimeTool() {
|
|
5793
6313
|
return tool({
|
|
5794
6314
|
description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
|
|
5795
6315
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5796
|
-
inputSchema:
|
|
6316
|
+
inputSchema: Type21.Object({}),
|
|
5797
6317
|
execute: async () => {
|
|
5798
6318
|
const now = /* @__PURE__ */ new Date();
|
|
5799
6319
|
return {
|
|
@@ -5811,7 +6331,7 @@ function createSystemTimeTool() {
|
|
|
5811
6331
|
import {
|
|
5812
6332
|
Agent
|
|
5813
6333
|
} from "@earendil-works/pi-agent-core";
|
|
5814
|
-
import { Type as
|
|
6334
|
+
import { Type as Type22 } from "@sinclair/typebox";
|
|
5815
6335
|
import { THREAD_STATE_TTL_MS } from "chat";
|
|
5816
6336
|
|
|
5817
6337
|
// src/chat/tools/advisor/session-store.ts
|
|
@@ -5899,12 +6419,12 @@ function createAdvisorTool(context) {
|
|
|
5899
6419
|
const spanContext = context.logContext ?? {};
|
|
5900
6420
|
return tool({
|
|
5901
6421
|
description: ADVISOR_TOOL_DESCRIPTION,
|
|
5902
|
-
inputSchema:
|
|
5903
|
-
question:
|
|
6422
|
+
inputSchema: Type22.Object({
|
|
6423
|
+
question: Type22.String({
|
|
5904
6424
|
minLength: 1,
|
|
5905
6425
|
description: "Focused advisor question or decision point."
|
|
5906
6426
|
}),
|
|
5907
|
-
context:
|
|
6427
|
+
context: Type22.String({
|
|
5908
6428
|
minLength: 1,
|
|
5909
6429
|
description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
|
|
5910
6430
|
})
|
|
@@ -5933,12 +6453,16 @@ function createAdvisorTool(context) {
|
|
|
5933
6453
|
const conversationId = context.conversationId;
|
|
5934
6454
|
const advisorSessionKey = getAdvisorSessionKey(conversationId);
|
|
5935
6455
|
const subagentInvocationId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : `advisor:${Date.now()}`;
|
|
5936
|
-
const endSubagent = async (outcome, errorCode) => recordSubagentEnded({
|
|
6456
|
+
const endSubagent = async (outcome, errorCode, transcriptRange) => recordSubagentEnded({
|
|
5937
6457
|
conversationId,
|
|
5938
6458
|
sessionId: context.parentSessionId,
|
|
5939
6459
|
subagentInvocationId,
|
|
5940
6460
|
outcome,
|
|
5941
6461
|
...errorCode ? { errorCode } : {},
|
|
6462
|
+
...transcriptRange ? {
|
|
6463
|
+
transcriptEndMessageIndex: transcriptRange.endMessageIndex,
|
|
6464
|
+
transcriptStartMessageIndex: transcriptRange.startMessageIndex
|
|
6465
|
+
} : {},
|
|
5942
6466
|
ttlMs: THREAD_STATE_TTL_MS
|
|
5943
6467
|
});
|
|
5944
6468
|
await recordSubagentStarted({
|
|
@@ -6057,7 +6581,10 @@ function createAdvisorTool(context) {
|
|
|
6057
6581
|
"Advisor guidance is unavailable because advisor history could not be saved. Retry the advisor call or continue without assuming advisor history."
|
|
6058
6582
|
);
|
|
6059
6583
|
}
|
|
6060
|
-
await endSubagent("success"
|
|
6584
|
+
await endSubagent("success", void 0, {
|
|
6585
|
+
endMessageIndex: advisorAgent.state.messages.length,
|
|
6586
|
+
startMessageIndex: beforeMessageCount
|
|
6587
|
+
});
|
|
6061
6588
|
setSpanStatus("ok");
|
|
6062
6589
|
return success(memo);
|
|
6063
6590
|
},
|
|
@@ -6080,7 +6607,7 @@ function createAdvisorTool(context) {
|
|
|
6080
6607
|
}
|
|
6081
6608
|
|
|
6082
6609
|
// src/chat/tools/web/fetch-tool.ts
|
|
6083
|
-
import { Type as
|
|
6610
|
+
import { Type as Type23 } from "@sinclair/typebox";
|
|
6084
6611
|
|
|
6085
6612
|
// src/chat/tools/web/constants.ts
|
|
6086
6613
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -6525,13 +7052,13 @@ function createWebFetchTool(hooks) {
|
|
|
6525
7052
|
destructiveHint: false,
|
|
6526
7053
|
openWorldHint: true
|
|
6527
7054
|
},
|
|
6528
|
-
inputSchema:
|
|
6529
|
-
url:
|
|
7055
|
+
inputSchema: Type23.Object({
|
|
7056
|
+
url: Type23.String({
|
|
6530
7057
|
minLength: 1,
|
|
6531
7058
|
description: "HTTP(S) URL to fetch."
|
|
6532
7059
|
}),
|
|
6533
|
-
max_chars:
|
|
6534
|
-
|
|
7060
|
+
max_chars: Type23.Optional(
|
|
7061
|
+
Type23.Integer({
|
|
6535
7062
|
minimum: 500,
|
|
6536
7063
|
maximum: MAX_FETCH_CHARS,
|
|
6537
7064
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -6595,7 +7122,7 @@ function createWebFetchTool(hooks) {
|
|
|
6595
7122
|
import { generateText } from "ai";
|
|
6596
7123
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
6597
7124
|
import { getModel } from "@earendil-works/pi-ai";
|
|
6598
|
-
import { Type as
|
|
7125
|
+
import { Type as Type24 } from "@sinclair/typebox";
|
|
6599
7126
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6600
7127
|
var MAX_RESULTS2 = 5;
|
|
6601
7128
|
var DEFAULT_SEARCH_MODEL = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
|
|
@@ -6643,14 +7170,14 @@ function createWebSearchTool(override) {
|
|
|
6643
7170
|
destructiveHint: false,
|
|
6644
7171
|
openWorldHint: true
|
|
6645
7172
|
},
|
|
6646
|
-
inputSchema:
|
|
6647
|
-
query:
|
|
7173
|
+
inputSchema: Type24.Object({
|
|
7174
|
+
query: Type24.String({
|
|
6648
7175
|
minLength: 1,
|
|
6649
7176
|
maxLength: 500,
|
|
6650
7177
|
description: "Search query."
|
|
6651
7178
|
}),
|
|
6652
|
-
max_results:
|
|
6653
|
-
|
|
7179
|
+
max_results: Type24.Optional(
|
|
7180
|
+
Type24.Integer({
|
|
6654
7181
|
minimum: 1,
|
|
6655
7182
|
maximum: MAX_RESULTS2,
|
|
6656
7183
|
description: "Max results to return."
|
|
@@ -6722,18 +7249,18 @@ function createWebSearchTool(override) {
|
|
|
6722
7249
|
}
|
|
6723
7250
|
|
|
6724
7251
|
// src/chat/tools/sandbox/write-file.ts
|
|
6725
|
-
import { Type as
|
|
7252
|
+
import { Type as Type25 } from "@sinclair/typebox";
|
|
6726
7253
|
function createWriteFileTool() {
|
|
6727
7254
|
return tool({
|
|
6728
7255
|
description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.",
|
|
6729
7256
|
executionMode: "sequential",
|
|
6730
|
-
inputSchema:
|
|
7257
|
+
inputSchema: Type25.Object(
|
|
6731
7258
|
{
|
|
6732
|
-
path:
|
|
7259
|
+
path: Type25.String({
|
|
6733
7260
|
minLength: 1,
|
|
6734
7261
|
description: "Path to write in the sandbox workspace."
|
|
6735
7262
|
}),
|
|
6736
|
-
content:
|
|
7263
|
+
content: Type25.String({
|
|
6737
7264
|
description: "UTF-8 file content to write."
|
|
6738
7265
|
})
|
|
6739
7266
|
},
|
|
@@ -6802,6 +7329,11 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6802
7329
|
if (context.advisor) {
|
|
6803
7330
|
tools.advisor = createAdvisorTool(context.advisor);
|
|
6804
7331
|
}
|
|
7332
|
+
if (canUseResourceEventSubscriptionTools(context)) {
|
|
7333
|
+
tools.subscribeToResourceEvents = createSubscribeToResourceEventsTool(context);
|
|
7334
|
+
tools.listResourceEventSubscriptions = createListResourceEventSubscriptionsTool(context);
|
|
7335
|
+
tools.cancelResourceEventSubscription = createCancelResourceEventSubscriptionTool(context);
|
|
7336
|
+
}
|
|
6805
7337
|
if (context.mcpToolManager) {
|
|
6806
7338
|
tools.searchMcpTools = createSearchMcpToolsTool(context.mcpToolManager);
|
|
6807
7339
|
tools.callMcpTool = createCallMcpToolTool(context.mcpToolManager);
|
|
@@ -7063,7 +7595,14 @@ async function deliverPrivateMessage(input) {
|
|
|
7063
7595
|
}
|
|
7064
7596
|
}
|
|
7065
7597
|
try {
|
|
7066
|
-
const dmChannelId = (await
|
|
7598
|
+
const dmChannelId = (await withSlackRetries(
|
|
7599
|
+
() => client.conversations.open({ users: input.userId }),
|
|
7600
|
+
3,
|
|
7601
|
+
{
|
|
7602
|
+
action: "conversations.open",
|
|
7603
|
+
spanAttributes: { "app.slack.user_id": input.userId }
|
|
7604
|
+
}
|
|
7605
|
+
)).channel?.id;
|
|
7067
7606
|
if (!dmChannelId) {
|
|
7068
7607
|
logWarn(
|
|
7069
7608
|
"oauth_dm_fallback_failed",
|
|
@@ -7167,59 +7706,59 @@ async function startOAuthFlow(provider, input) {
|
|
|
7167
7706
|
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
7168
7707
|
|
|
7169
7708
|
// src/chat/sandbox/egress/schemas.ts
|
|
7170
|
-
import { z } from "zod";
|
|
7709
|
+
import { z as z2 } from "zod";
|
|
7171
7710
|
import {
|
|
7172
7711
|
pluginAuthorizationSchema,
|
|
7173
7712
|
pluginCredentialHeaderTransformSchema,
|
|
7174
7713
|
pluginGrantSchema,
|
|
7175
7714
|
pluginProviderAccountSchema
|
|
7176
7715
|
} from "@sentry/junior-plugin-api";
|
|
7177
|
-
var finiteNumberSchema =
|
|
7178
|
-
var httpStatusSchema =
|
|
7179
|
-
var providerNameSchema =
|
|
7180
|
-
var credentialSignalKindSchema =
|
|
7716
|
+
var finiteNumberSchema = z2.number().refine(Number.isFinite);
|
|
7717
|
+
var httpStatusSchema = z2.number().int().min(100).max(599);
|
|
7718
|
+
var providerNameSchema = z2.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
7719
|
+
var credentialSignalKindSchema = z2.enum(["auth_required", "unavailable"]);
|
|
7181
7720
|
var sandboxEgressGrantSchema = pluginGrantSchema;
|
|
7182
|
-
var sandboxEgressCredentialContextSchema =
|
|
7721
|
+
var sandboxEgressCredentialContextSchema = z2.object({
|
|
7183
7722
|
credentials: credentialContextSchema,
|
|
7184
|
-
egressId:
|
|
7723
|
+
egressId: z2.string().min(1),
|
|
7185
7724
|
expiresAtMs: finiteNumberSchema,
|
|
7186
|
-
contextId:
|
|
7725
|
+
contextId: z2.string().min(1)
|
|
7187
7726
|
}).strict();
|
|
7188
|
-
var sandboxEgressCredentialLeaseSchema =
|
|
7727
|
+
var sandboxEgressCredentialLeaseSchema = z2.object({
|
|
7189
7728
|
account: pluginProviderAccountSchema.optional(),
|
|
7190
7729
|
authorization: pluginAuthorizationSchema.optional(),
|
|
7191
7730
|
grant: sandboxEgressGrantSchema,
|
|
7192
7731
|
provider: providerNameSchema,
|
|
7193
|
-
expiresAt:
|
|
7194
|
-
headerTransforms:
|
|
7732
|
+
expiresAt: z2.string().min(1),
|
|
7733
|
+
headerTransforms: z2.array(pluginCredentialHeaderTransformSchema).min(1)
|
|
7195
7734
|
}).strict();
|
|
7196
|
-
var sandboxEgressAuthRequiredSignalSchema =
|
|
7735
|
+
var sandboxEgressAuthRequiredSignalSchema = z2.object({
|
|
7197
7736
|
authorization: pluginAuthorizationSchema.optional(),
|
|
7198
7737
|
grant: sandboxEgressGrantSchema,
|
|
7199
7738
|
kind: credentialSignalKindSchema.default("auth_required"),
|
|
7200
7739
|
provider: providerNameSchema,
|
|
7201
|
-
message:
|
|
7740
|
+
message: z2.string().optional(),
|
|
7202
7741
|
createdAtMs: finiteNumberSchema
|
|
7203
7742
|
}).strict().superRefine((signal, ctx) => {
|
|
7204
7743
|
if (signal.authorization && signal.authorization.provider !== signal.provider) {
|
|
7205
7744
|
ctx.addIssue({
|
|
7206
|
-
code:
|
|
7745
|
+
code: z2.ZodIssueCode.custom,
|
|
7207
7746
|
message: "Auth signal authorization provider must match provider",
|
|
7208
7747
|
path: ["authorization", "provider"]
|
|
7209
7748
|
});
|
|
7210
7749
|
}
|
|
7211
7750
|
});
|
|
7212
|
-
var sandboxEgressPermissionDeniedSignalSchema =
|
|
7751
|
+
var sandboxEgressPermissionDeniedSignalSchema = z2.object({
|
|
7213
7752
|
account: pluginProviderAccountSchema.optional(),
|
|
7214
|
-
acceptedPermissions:
|
|
7753
|
+
acceptedPermissions: z2.string().optional(),
|
|
7215
7754
|
grant: sandboxEgressGrantSchema,
|
|
7216
|
-
message:
|
|
7755
|
+
message: z2.string().min(1),
|
|
7217
7756
|
provider: providerNameSchema,
|
|
7218
|
-
source:
|
|
7219
|
-
sso:
|
|
7757
|
+
source: z2.literal("upstream"),
|
|
7758
|
+
sso: z2.string().optional(),
|
|
7220
7759
|
status: httpStatusSchema,
|
|
7221
|
-
upstreamHost:
|
|
7222
|
-
upstreamPath:
|
|
7760
|
+
upstreamHost: z2.string().min(1),
|
|
7761
|
+
upstreamPath: z2.string().min(1),
|
|
7223
7762
|
createdAtMs: finiteNumberSchema
|
|
7224
7763
|
}).strict();
|
|
7225
7764
|
function parseSandboxEgressAuthRequiredSignal(value) {
|
|
@@ -7296,12 +7835,12 @@ function getSandboxEgressSecret() {
|
|
|
7296
7835
|
throw new Error("Cannot determine sandbox egress secret (set JUNIOR_SECRET)");
|
|
7297
7836
|
}
|
|
7298
7837
|
function createSandboxEgressCredentialToken(input) {
|
|
7299
|
-
const
|
|
7838
|
+
const ttlMs2 = Math.max(1, input.ttlMs ?? DEFAULT_SESSION_TTL_MS);
|
|
7300
7839
|
const now = Date.now();
|
|
7301
7840
|
const context = {
|
|
7302
7841
|
credentials: input.credentials,
|
|
7303
7842
|
egressId: input.egressId,
|
|
7304
|
-
expiresAtMs: now +
|
|
7843
|
+
expiresAtMs: now + ttlMs2,
|
|
7305
7844
|
contextId: randomUUID()
|
|
7306
7845
|
};
|
|
7307
7846
|
const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
|
|
@@ -7337,7 +7876,7 @@ async function setSandboxEgressCredentialLease(context, lease) {
|
|
|
7337
7876
|
if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
|
|
7338
7877
|
return;
|
|
7339
7878
|
}
|
|
7340
|
-
const
|
|
7879
|
+
const ttlMs2 = Math.max(
|
|
7341
7880
|
1,
|
|
7342
7881
|
Math.min(leaseExpiresAtMs, context.expiresAtMs) - Date.now()
|
|
7343
7882
|
);
|
|
@@ -7346,7 +7885,7 @@ async function setSandboxEgressCredentialLease(context, lease) {
|
|
|
7346
7885
|
await state.set(
|
|
7347
7886
|
leaseKey(lease.provider, lease.grant.name, context),
|
|
7348
7887
|
lease,
|
|
7349
|
-
|
|
7888
|
+
ttlMs2
|
|
7350
7889
|
);
|
|
7351
7890
|
}
|
|
7352
7891
|
async function getSandboxEgressCredentialLease(provider, grantName, context) {
|
|
@@ -7360,7 +7899,7 @@ async function clearSandboxEgressCredentialLease(provider, grantName, context) {
|
|
|
7360
7899
|
await state.delete(leaseKey(provider, grantName, context));
|
|
7361
7900
|
}
|
|
7362
7901
|
async function setSandboxEgressAuthRequiredSignal(context, signal) {
|
|
7363
|
-
const
|
|
7902
|
+
const ttlMs2 = Math.max(1, context.expiresAtMs - Date.now());
|
|
7364
7903
|
const state = getStateAdapter();
|
|
7365
7904
|
await state.connect();
|
|
7366
7905
|
await state.set(
|
|
@@ -7369,11 +7908,11 @@ async function setSandboxEgressAuthRequiredSignal(context, signal) {
|
|
|
7369
7908
|
...signal,
|
|
7370
7909
|
createdAtMs: Date.now()
|
|
7371
7910
|
},
|
|
7372
|
-
|
|
7911
|
+
ttlMs2
|
|
7373
7912
|
);
|
|
7374
7913
|
}
|
|
7375
7914
|
async function setSandboxEgressPermissionDeniedSignal(context, signal) {
|
|
7376
|
-
const
|
|
7915
|
+
const ttlMs2 = Math.max(1, context.expiresAtMs - Date.now());
|
|
7377
7916
|
const state = getStateAdapter();
|
|
7378
7917
|
await state.connect();
|
|
7379
7918
|
await state.set(
|
|
@@ -7382,7 +7921,7 @@ async function setSandboxEgressPermissionDeniedSignal(context, signal) {
|
|
|
7382
7921
|
...signal,
|
|
7383
7922
|
createdAtMs: Date.now()
|
|
7384
7923
|
},
|
|
7385
|
-
|
|
7924
|
+
ttlMs2
|
|
7386
7925
|
);
|
|
7387
7926
|
}
|
|
7388
7927
|
async function clearSandboxEgressSignals(egressId) {
|
|
@@ -9383,12 +9922,12 @@ async function getMcpAuthSession(authSessionId) {
|
|
|
9383
9922
|
const stateAdapter = await getConnectedStateAdapter();
|
|
9384
9923
|
return parseMcpAuthSession(await stateAdapter.get(sessionKey(authSessionId)));
|
|
9385
9924
|
}
|
|
9386
|
-
async function putMcpAuthSession(session,
|
|
9925
|
+
async function putMcpAuthSession(session, ttlMs2 = MCP_AUTH_SESSION_TTL_MS) {
|
|
9387
9926
|
const stateAdapter = await getConnectedStateAdapter();
|
|
9388
9927
|
await stateAdapter.set(
|
|
9389
9928
|
sessionKey(session.authSessionId),
|
|
9390
9929
|
JSON.stringify(session),
|
|
9391
|
-
|
|
9930
|
+
ttlMs2
|
|
9392
9931
|
);
|
|
9393
9932
|
const nextIndex = parseSessionIndex(
|
|
9394
9933
|
await stateAdapter.get(sessionIndexKey(session.userId, session.provider))
|
|
@@ -9399,7 +9938,7 @@ async function putMcpAuthSession(session, ttlMs = MCP_AUTH_SESSION_TTL_MS) {
|
|
|
9399
9938
|
await stateAdapter.set(
|
|
9400
9939
|
sessionIndexKey(session.userId, session.provider),
|
|
9401
9940
|
JSON.stringify(nextIndex),
|
|
9402
|
-
|
|
9941
|
+
ttlMs2
|
|
9403
9942
|
);
|
|
9404
9943
|
}
|
|
9405
9944
|
async function patchMcpAuthSession(authSessionId, patch) {
|
|
@@ -9479,12 +10018,12 @@ async function getMcpStoredOAuthCredentials(userId, provider) {
|
|
|
9479
10018
|
await stateAdapter.get(credentialsKey(userId, provider))
|
|
9480
10019
|
);
|
|
9481
10020
|
}
|
|
9482
|
-
async function putMcpStoredOAuthCredentials(userId, provider, value,
|
|
10021
|
+
async function putMcpStoredOAuthCredentials(userId, provider, value, ttlMs2 = MCP_AUTH_CREDENTIALS_TTL_MS) {
|
|
9483
10022
|
const stateAdapter = await getConnectedStateAdapter();
|
|
9484
10023
|
await stateAdapter.set(
|
|
9485
10024
|
credentialsKey(userId, provider),
|
|
9486
10025
|
JSON.stringify(value),
|
|
9487
|
-
|
|
10026
|
+
ttlMs2
|
|
9488
10027
|
);
|
|
9489
10028
|
}
|
|
9490
10029
|
async function deleteMcpStoredOAuthCredentials(userId, provider) {
|
|
@@ -9497,7 +10036,7 @@ async function getMcpServerSessionId(userId, provider) {
|
|
|
9497
10036
|
await stateAdapter.get(serverSessionKey(userId, provider))
|
|
9498
10037
|
)?.sessionId;
|
|
9499
10038
|
}
|
|
9500
|
-
async function putMcpServerSessionId(userId, provider, sessionId,
|
|
10039
|
+
async function putMcpServerSessionId(userId, provider, sessionId, ttlMs2 = MCP_SERVER_SESSION_TTL_MS) {
|
|
9501
10040
|
const stateAdapter = await getConnectedStateAdapter();
|
|
9502
10041
|
await stateAdapter.set(
|
|
9503
10042
|
serverSessionKey(userId, provider),
|
|
@@ -9505,7 +10044,7 @@ async function putMcpServerSessionId(userId, provider, sessionId, ttlMs = MCP_SE
|
|
|
9505
10044
|
sessionId,
|
|
9506
10045
|
updatedAtMs: Date.now()
|
|
9507
10046
|
}),
|
|
9508
|
-
|
|
10047
|
+
ttlMs2
|
|
9509
10048
|
);
|
|
9510
10049
|
}
|
|
9511
10050
|
async function deleteMcpServerSessionId(userId, provider) {
|
|
@@ -9525,6 +10064,15 @@ async function unlinkProvider(userId, provider, userTokenStore) {
|
|
|
9525
10064
|
|
|
9526
10065
|
// src/chat/services/pending-auth.ts
|
|
9527
10066
|
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
10067
|
+
var NON_REQUEST_SKIPPED_REASON_PREFIXES = [
|
|
10068
|
+
"directed_to_other_party:",
|
|
10069
|
+
"side_conversation:"
|
|
10070
|
+
];
|
|
10071
|
+
function isSkippedNonRequest(reason) {
|
|
10072
|
+
return NON_REQUEST_SKIPPED_REASON_PREFIXES.some(
|
|
10073
|
+
(prefix) => reason?.startsWith(prefix)
|
|
10074
|
+
);
|
|
10075
|
+
}
|
|
9528
10076
|
function canReusePendingAuthLink(args) {
|
|
9529
10077
|
const { pendingAuth } = args;
|
|
9530
10078
|
if (!pendingAuth) {
|
|
@@ -9568,6 +10116,12 @@ function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
|
9568
10116
|
if (message?.role !== "user") {
|
|
9569
10117
|
continue;
|
|
9570
10118
|
}
|
|
10119
|
+
if (message.author?.isBot) {
|
|
10120
|
+
continue;
|
|
10121
|
+
}
|
|
10122
|
+
if (isSkippedNonRequest(message.meta?.skippedReason)) {
|
|
10123
|
+
continue;
|
|
10124
|
+
}
|
|
9571
10125
|
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
9572
10126
|
}
|
|
9573
10127
|
return false;
|
|
@@ -10207,7 +10761,7 @@ function buildTurnResult(input) {
|
|
|
10207
10761
|
}
|
|
10208
10762
|
|
|
10209
10763
|
// src/chat/services/turn-thinking-level.ts
|
|
10210
|
-
import { z as
|
|
10764
|
+
import { z as z3 } from "zod";
|
|
10211
10765
|
var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
|
|
10212
10766
|
var MAX_ROUTER_CONTEXT_CHARS = 8e3;
|
|
10213
10767
|
var ROUTER_CONTEXT_HEAD_CHARS = 3e3;
|
|
@@ -10239,13 +10793,13 @@ function coerceClassifierConfidence(value) {
|
|
|
10239
10793
|
}
|
|
10240
10794
|
return CONFIDENCE_LABELS[trimmed] ?? value;
|
|
10241
10795
|
}
|
|
10242
|
-
var turnExecutionProfileSchema =
|
|
10243
|
-
thinking_level:
|
|
10244
|
-
confidence:
|
|
10796
|
+
var turnExecutionProfileSchema = z3.object({
|
|
10797
|
+
thinking_level: z3.enum(TURN_THINKING_LEVELS),
|
|
10798
|
+
confidence: z3.preprocess(
|
|
10245
10799
|
coerceClassifierConfidence,
|
|
10246
|
-
|
|
10800
|
+
z3.number().min(0).max(1)
|
|
10247
10801
|
),
|
|
10248
|
-
reason:
|
|
10802
|
+
reason: z3.string().min(1)
|
|
10249
10803
|
});
|
|
10250
10804
|
var DEFAULT_THINKING_LEVEL = "medium";
|
|
10251
10805
|
var THINKING_LEVEL_RANK = {
|
|
@@ -14215,6 +14769,8 @@ export {
|
|
|
14215
14769
|
setConfigDefaults,
|
|
14216
14770
|
getConfigDefaults,
|
|
14217
14771
|
createUserTokenStore,
|
|
14772
|
+
findMatchingResourceEventSubscriptions,
|
|
14773
|
+
deliverResourceEventSubscription,
|
|
14218
14774
|
listThreadReplies,
|
|
14219
14775
|
postSlackMessage,
|
|
14220
14776
|
deleteSlackMessage,
|