@cursor/july 0.1.49 → 0.1.52
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/AGENTS.md +3 -3
- package/README.md +7 -14
- package/dist/bin/agent-serve.js +22 -8
- package/dist/channels/slack/init.d.ts +2 -3
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +8 -43
- package/dist/channels/slack/manifest.d.ts +3 -3
- package/dist/channels/slack/manifest.js +3 -3
- package/dist/channels/slack/setup.d.ts +2 -11
- package/dist/channels/slack/setup.d.ts.map +1 -1
- package/dist/channels/slack/setup.js +26 -94
- package/dist/channels/slack/types.d.ts +3 -4
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.BpWAgjaR.js → app.Cbeq6FOy.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.TzAgoWLl.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CnAktZtG.js → VPLocalSearchBox.Bmb8QjCI.js} +1 -1
- package/dist/docs/assets/chunks/{theme.B5IvUxOB.js → theme.BLNMeCRL.js} +2 -2
- package/dist/docs/assets/{deployment.md.Cdp4rTE4.js → deployment.md.B8kW-h7P.js} +1 -1
- package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.js → example-agents_benny.md.B-LIDGja.js} +1 -1
- package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.js → example-agents_oncall.md.CBmyuAKc.js} +2 -2
- package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.js → guides_human-in-the-loop.md.Cf8kIIqC.js} +2 -2
- package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.lean.js → guides_human-in-the-loop.md.Cf8kIIqC.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.zriQpU_9.js → guides_slack.md.D2nTRu9M.js} +7 -12
- package/dist/docs/assets/guides_slack.md.D2nTRu9M.lean.js +1 -0
- package/dist/docs/assets/{reference_cli.md.BDmjPPAm.js → reference_cli.md.CGKfz_Yj.js} +3 -7
- package/dist/docs/assets/{reference_cli.md.BDmjPPAm.lean.js → reference_cli.md.CGKfz_Yj.lean.js} +1 -1
- package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.js → troubleshooting.md.C6n3wCr6.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +5 -5
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +5 -5
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +6 -6
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +5 -5
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +11 -16
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +6 -10
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/internal/cli-ax.js +3 -3
- package/dist/internal/cli-cursor.d.ts +18 -0
- package/dist/internal/cli-cursor.d.ts.map +1 -1
- package/dist/internal/cli-cursor.js +25 -0
- package/dist/internal/cli-slack.d.ts +3 -4
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +126 -322
- package/dist/internal/convert-automation/convert-workflow.js +2 -2
- package/dist/internal/event-log.d.ts +37 -17
- package/dist/internal/event-log.d.ts.map +1 -1
- package/dist/internal/event-log.js +189 -55
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +2 -2
- package/dist/internal/slack-provision-client.d.ts +37 -2
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +46 -0
- package/dist/playground/assets/{index-Dq_HqyU-.js → index-BKMz70wx.js} +39 -39
- package/dist/playground/assets/index-BmMrulSc.css +1 -0
- package/dist/playground/index.html +2 -2
- package/docs/deployment.md +1 -1
- package/docs/example-agents/benny.md +2 -3
- package/docs/example-agents/oncall.md +4 -5
- package/docs/guides/human-in-the-loop.md +1 -1
- package/docs/guides/slack.md +51 -112
- package/docs/reference/cli.md +14 -37
- package/docs/troubleshooting.md +2 -2
- package/package.json +1 -1
- package/skills/create-agent/SKILL.md +4 -5
- package/skills/setup-slack/SKILL.md +73 -131
- package/src/bin/agent-serve.ts +26 -9
- package/src/channels/slack/init.ts +10 -71
- package/src/channels/slack/manifest.ts +3 -3
- package/src/channels/slack/setup.ts +25 -103
- package/src/channels/slack/types.ts +3 -4
- package/src/internal/cli-ax.ts +3 -3
- package/src/internal/cli-cursor.ts +34 -0
- package/src/internal/cli-slack.ts +136 -389
- package/src/internal/convert-automation/convert-workflow.ts +2 -2
- package/src/internal/event-log.ts +167 -52
- package/src/internal/session-engine.ts +4 -2
- package/src/internal/slack-provision-client.ts +83 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.D00d772K.js +0 -1
- package/dist/docs/assets/guides_slack.md.zriQpU_9.lean.js +0 -1
- package/dist/playground/assets/index-Dox74yWJ.css +0 -1
- /package/dist/docs/assets/{deployment.md.Cdp4rTE4.lean.js → deployment.md.B8kW-h7P.lean.js} +0 -0
- /package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.lean.js → example-agents_benny.md.B-LIDGja.lean.js} +0 -0
- /package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.lean.js → example-agents_oncall.md.CBmyuAKc.lean.js} +0 -0
- /package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.lean.js → troubleshooting.md.C6n3wCr6.lean.js} +0 -0
|
@@ -999,7 +999,7 @@ function emitSlackChannel(
|
|
|
999
999
|
}
|
|
1000
1000
|
if (socketMode) {
|
|
1001
1001
|
out.setupSteps.push(
|
|
1002
|
-
`Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app (not the Cursor connection): run \`${CLI_COMMAND_NAME} slack
|
|
1002
|
+
`Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app (not the Cursor connection): run \`${CLI_COMMAND_NAME} slack create --channel-posts\`, then finish in the dashboard wizard.`
|
|
1003
1003
|
);
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
@@ -1069,7 +1069,7 @@ function emitSlackChannel(
|
|
|
1069
1069
|
body = [
|
|
1070
1070
|
"//",
|
|
1071
1071
|
"// This channel needs a dedicated Socket Mode Slack app (not the Cursor",
|
|
1072
|
-
`// connection): run \`${CLI_COMMAND_NAME} slack
|
|
1072
|
+
`// connection): run \`${CLI_COMMAND_NAME} slack create --channel-posts\`.`,
|
|
1073
1073
|
"export default slackChannel({",
|
|
1074
1074
|
...engagement,
|
|
1075
1075
|
"});",
|
|
@@ -1,20 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Durable, replayable per-session event log.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Durable, replayable per-session event log. `events.ndjson` is the source
|
|
3
|
+
* of truth. The process keeps `nextIndex`, live subscribers, and a short
|
|
4
|
+
* unflushed tail — not a cache of the full transcript. Replay reads the
|
|
5
|
+
* file on demand; live streams subscribe first, then catch up by index.
|
|
6
|
+
*
|
|
7
|
+
* Hosted engines mint thousands of sessions (one per PR under
|
|
8
|
+
* agentic-owners). Retaining every streamed tool output in RAM for the
|
|
9
|
+
* process lifetime is what OOM'd those engines at the ~4 GB heap cap.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
import { appendFile, mkdir,
|
|
12
|
+
import { appendFile, type FileHandle, mkdir, open } from "node:fs/promises";
|
|
9
13
|
import { dirname } from "node:path";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
10
15
|
import type { SessionEvent, SessionEventPayload } from "../types.js";
|
|
11
16
|
|
|
12
17
|
export type AppendableEvent = SessionEventPayload & { turnId?: string };
|
|
13
18
|
|
|
19
|
+
function isEnoent(error: unknown): boolean {
|
|
20
|
+
return (
|
|
21
|
+
typeof error === "object" &&
|
|
22
|
+
error !== null &&
|
|
23
|
+
"code" in error &&
|
|
24
|
+
(error as { code: unknown }).code === "ENOENT"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseEventLine(line: string): SessionEvent | undefined {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (trimmed === "") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(trimmed) as SessionEvent;
|
|
35
|
+
} catch {
|
|
36
|
+
// Skip torn trailing writes from a crashed process.
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readNdjsonEvents(
|
|
42
|
+
filePath: string,
|
|
43
|
+
onEvent: (event: SessionEvent) => void
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
let fh: FileHandle;
|
|
46
|
+
try {
|
|
47
|
+
fh = await open(filePath, "r");
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (isEnoent(error)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
const stream = fh.createReadStream({ encoding: "utf8" });
|
|
55
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
56
|
+
try {
|
|
57
|
+
for await (const line of rl) {
|
|
58
|
+
const event = parseEventLine(line);
|
|
59
|
+
if (event !== undefined) {
|
|
60
|
+
onEvent(event);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
rl.close();
|
|
65
|
+
stream.destroy();
|
|
66
|
+
await fh.close().catch(() => {});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
14
70
|
export class SessionEventLog {
|
|
15
|
-
private
|
|
71
|
+
private nextIndex = 0;
|
|
16
72
|
private readonly listeners = new Set<(event: SessionEvent) => void>();
|
|
17
73
|
private persistChain: Promise<void> = Promise.resolve();
|
|
74
|
+
/** Appended events whose `appendFile` has not finished. */
|
|
75
|
+
private readonly inflight: SessionEvent[] = [];
|
|
76
|
+
/**
|
|
77
|
+
* In-process patches (cloud cost backfill) that are not rewritten to
|
|
78
|
+
* `events.ndjson`. Applied on read.
|
|
79
|
+
*/
|
|
80
|
+
private readonly replacements = new Map<number, SessionEvent>();
|
|
18
81
|
|
|
19
82
|
private constructor(
|
|
20
83
|
private readonly sessionId: string,
|
|
@@ -32,59 +95,52 @@ export class SessionEventLog {
|
|
|
32
95
|
args.filePath,
|
|
33
96
|
args.onPersistError ?? (() => {})
|
|
34
97
|
);
|
|
35
|
-
await log.
|
|
98
|
+
await log.readNextIndex();
|
|
36
99
|
return log;
|
|
37
100
|
}
|
|
38
101
|
|
|
39
|
-
private async
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
for (const line of raw.split("\n")) {
|
|
47
|
-
if (line.trim() === "") {
|
|
48
|
-
continue;
|
|
102
|
+
private async readNextIndex(): Promise<void> {
|
|
103
|
+
let next = 0;
|
|
104
|
+
await readNdjsonEvents(this.filePath, (event) => {
|
|
105
|
+
if (typeof event.index === "number" && event.index >= next) {
|
|
106
|
+
next = event.index + 1;
|
|
49
107
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} catch {
|
|
53
|
-
// Skip torn trailing writes from a crashed process.
|
|
54
|
-
}
|
|
55
|
-
}
|
|
108
|
+
});
|
|
109
|
+
this.nextIndex = next;
|
|
56
110
|
}
|
|
57
111
|
|
|
58
112
|
get count(): number {
|
|
59
|
-
return this.
|
|
113
|
+
return this.nextIndex;
|
|
60
114
|
}
|
|
61
115
|
|
|
62
116
|
/**
|
|
63
|
-
* In-process residency vs recorded length.
|
|
64
|
-
* (`
|
|
65
|
-
*
|
|
66
|
-
* the unflushed tail.
|
|
117
|
+
* In-process residency vs recorded length. `eventsTotal` is the durable
|
|
118
|
+
* count (`nextIndex`). `eventsRetained` is the unflushed tail — the
|
|
119
|
+
* transcript is not cached after `appendFile` lands.
|
|
67
120
|
*/
|
|
68
121
|
memoryStats(): { eventsRetained: number; eventsTotal: number } {
|
|
69
|
-
|
|
70
|
-
|
|
122
|
+
return {
|
|
123
|
+
eventsRetained: this.inflight.length,
|
|
124
|
+
eventsTotal: this.nextIndex,
|
|
125
|
+
};
|
|
71
126
|
}
|
|
72
127
|
|
|
73
128
|
/**
|
|
74
|
-
* Assign the next index
|
|
75
|
-
*
|
|
76
|
-
*
|
|
129
|
+
* Assign the next index and fan out to live subscribers. Ordering is
|
|
130
|
+
* synchronous; file persistence is chained in the background and awaited
|
|
131
|
+
* by {@link flush}. The event is not retained after the write lands.
|
|
77
132
|
*/
|
|
78
133
|
append(payload: AppendableEvent): SessionEvent {
|
|
79
134
|
const { turnId, ...rest } = payload;
|
|
80
135
|
const event = {
|
|
81
|
-
index: this.
|
|
136
|
+
index: this.nextIndex,
|
|
82
137
|
sessionId: this.sessionId,
|
|
83
138
|
...(turnId === undefined ? {} : { turnId }),
|
|
84
139
|
at: new Date().toISOString(),
|
|
85
140
|
...rest,
|
|
86
141
|
} as SessionEvent;
|
|
87
|
-
this.
|
|
142
|
+
this.nextIndex += 1;
|
|
143
|
+
this.inflight.push(event);
|
|
88
144
|
for (const listener of [...this.listeners]) {
|
|
89
145
|
try {
|
|
90
146
|
listener(event);
|
|
@@ -93,35 +149,67 @@ export class SessionEventLog {
|
|
|
93
149
|
}
|
|
94
150
|
}
|
|
95
151
|
const line = `${JSON.stringify(event)}\n`;
|
|
152
|
+
const index = event.index;
|
|
96
153
|
this.persistChain = this.persistChain
|
|
97
154
|
.then(() => appendFile(this.filePath, line, "utf8"))
|
|
155
|
+
.then(() => {
|
|
156
|
+
this.dropInflight(index);
|
|
157
|
+
})
|
|
98
158
|
.catch((error) => this.onPersistError(error));
|
|
99
159
|
return event;
|
|
100
160
|
}
|
|
101
161
|
|
|
162
|
+
private dropInflight(index: number): void {
|
|
163
|
+
if (this.inflight[0]?.index === index) {
|
|
164
|
+
this.inflight.shift();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const i = this.inflight.findIndex((event) => event.index === index);
|
|
168
|
+
if (i >= 0) {
|
|
169
|
+
this.inflight.splice(i, 1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
102
173
|
/** Wait for pending file writes (used on close). */
|
|
103
174
|
async flush(): Promise<void> {
|
|
104
175
|
await this.persistChain;
|
|
105
176
|
}
|
|
106
177
|
|
|
107
|
-
|
|
108
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Read recorded events from `startIndex`. Does not retain the result on
|
|
180
|
+
* the log; callers own the returned array.
|
|
181
|
+
*/
|
|
182
|
+
async snapshot(startIndex = 0): Promise<SessionEvent[]> {
|
|
183
|
+
const byIndex = new Map<number, SessionEvent>();
|
|
184
|
+
await readNdjsonEvents(this.filePath, (event) => {
|
|
185
|
+
if (event.index >= startIndex) {
|
|
186
|
+
byIndex.set(event.index, event);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
for (const event of this.inflight) {
|
|
190
|
+
if (event.index >= startIndex) {
|
|
191
|
+
byIndex.set(event.index, event);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
for (const [index, event] of this.replacements) {
|
|
195
|
+
if (index >= startIndex) {
|
|
196
|
+
byIndex.set(index, event);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return [...byIndex.values()].sort((a, b) => a.index - b.index);
|
|
109
200
|
}
|
|
110
201
|
|
|
111
202
|
/**
|
|
112
|
-
* Replace one already-recorded event in
|
|
113
|
-
* onto historical `turn.completed` rows without rewriting
|
|
114
|
-
* Live subscribers are not notified — patch before
|
|
203
|
+
* Replace one already-recorded event in this process. Used to backfill
|
|
204
|
+
* usage/cost onto historical `turn.completed` rows without rewriting
|
|
205
|
+
* `events.ndjson`. Live subscribers are not notified — patch before
|
|
206
|
+
* opening a replay stream.
|
|
115
207
|
*/
|
|
116
208
|
replace(index: number, event: SessionEvent): boolean {
|
|
117
|
-
if (index < 0 || index >= this.
|
|
209
|
+
if (index < 0 || index >= this.nextIndex || event.index !== index) {
|
|
118
210
|
return false;
|
|
119
211
|
}
|
|
120
|
-
|
|
121
|
-
if (current === undefined || current.index !== index) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
this.events[index] = event;
|
|
212
|
+
this.replacements.set(index, event);
|
|
125
213
|
return true;
|
|
126
214
|
}
|
|
127
215
|
|
|
@@ -135,12 +223,16 @@ export class SessionEventLog {
|
|
|
135
223
|
/**
|
|
136
224
|
* NDJSON stream: replays recorded events from `startIndex`, then follows
|
|
137
225
|
* live events until the consumer cancels.
|
|
226
|
+
*
|
|
227
|
+
* Subscribe first (buffer live events), then read the file, then drain
|
|
228
|
+
* the buffer by index so an async file read cannot drop or duplicate
|
|
229
|
+
* events that arrive during catch-up.
|
|
138
230
|
*/
|
|
139
231
|
stream(startIndex = 0): ReadableStream<Uint8Array> {
|
|
140
232
|
const encoder = new TextEncoder();
|
|
141
233
|
let unsubscribe: (() => void) | undefined;
|
|
142
234
|
return new ReadableStream<Uint8Array>({
|
|
143
|
-
start: (controller) => {
|
|
235
|
+
start: async (controller) => {
|
|
144
236
|
const push = (event: SessionEvent): void => {
|
|
145
237
|
try {
|
|
146
238
|
controller.enqueue(encoder.encode(`${JSON.stringify(event)}\n`));
|
|
@@ -148,15 +240,38 @@ export class SessionEventLog {
|
|
|
148
240
|
unsubscribe?.();
|
|
149
241
|
}
|
|
150
242
|
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
push(event);
|
|
154
|
-
}
|
|
243
|
+
let following = false;
|
|
244
|
+
const buffered: SessionEvent[] = [];
|
|
155
245
|
unsubscribe = this.subscribe((event) => {
|
|
156
|
-
if (event.index
|
|
246
|
+
if (event.index < startIndex) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (following) {
|
|
157
250
|
push(event);
|
|
251
|
+
} else {
|
|
252
|
+
buffered.push(event);
|
|
158
253
|
}
|
|
159
254
|
});
|
|
255
|
+
try {
|
|
256
|
+
const prefix = await this.snapshot(startIndex);
|
|
257
|
+
let maxSeen = startIndex - 1;
|
|
258
|
+
for (const event of prefix) {
|
|
259
|
+
push(event);
|
|
260
|
+
if (event.index > maxSeen) {
|
|
261
|
+
maxSeen = event.index;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
following = true;
|
|
265
|
+
for (const event of buffered) {
|
|
266
|
+
if (event.index > maxSeen) {
|
|
267
|
+
push(event);
|
|
268
|
+
maxSeen = event.index;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
unsubscribe?.();
|
|
273
|
+
controller.error(error);
|
|
274
|
+
}
|
|
160
275
|
},
|
|
161
276
|
cancel: () => {
|
|
162
277
|
unsubscribe?.();
|
|
@@ -3014,7 +3014,7 @@ export class SessionEngine {
|
|
|
3014
3014
|
|
|
3015
3015
|
private async runCloudTurnCostBackfill(sessionId: string): Promise<void> {
|
|
3016
3016
|
const log = await this.logs.get(sessionId);
|
|
3017
|
-
const events = log.snapshot(0);
|
|
3017
|
+
const events = await log.snapshot(0);
|
|
3018
3018
|
if (!events.some((event) => isUnpricedCompletedTurn(event))) {
|
|
3019
3019
|
if (
|
|
3020
3020
|
!this.isTurnRunning(sessionId) &&
|
|
@@ -3049,7 +3049,9 @@ export class SessionEngine {
|
|
|
3049
3049
|
}
|
|
3050
3050
|
log.replace(i, next);
|
|
3051
3051
|
}
|
|
3052
|
-
if (
|
|
3052
|
+
if (
|
|
3053
|
+
!(await log.snapshot(0)).some((event) => isUnpricedCompletedTurn(event))
|
|
3054
|
+
) {
|
|
3053
3055
|
this.cloudCostBackfillDone.add(sessionId);
|
|
3054
3056
|
}
|
|
3055
3057
|
}
|
|
@@ -55,8 +55,8 @@ export type SlackAppIconInput =
|
|
|
55
55
|
* Outcome of `POST /apps`. Credentials are returned exactly once and never
|
|
56
56
|
* persisted server-side — the caller owns storing them. `appToken` can be
|
|
57
57
|
* absent when Slack's install response omitted the app-level token.
|
|
58
|
-
* `oauthAuthorizeUrl` is
|
|
59
|
-
*
|
|
58
|
+
* `oauthAuthorizeUrl` is Slack's request-approval / reminder page. Fresh
|
|
59
|
+
* creates mint it; updates return it when Slack (or a persisted row) has one.
|
|
60
60
|
*/
|
|
61
61
|
export type SlackAppProvisionResult =
|
|
62
62
|
| {
|
|
@@ -176,6 +176,66 @@ const setIconResponseSchema = z
|
|
|
176
176
|
})
|
|
177
177
|
.passthrough();
|
|
178
178
|
|
|
179
|
+
const mintWizardResponseSchema = z
|
|
180
|
+
.object({
|
|
181
|
+
wizardId: z.string().min(1),
|
|
182
|
+
url: z.string().min(1),
|
|
183
|
+
})
|
|
184
|
+
.passthrough();
|
|
185
|
+
|
|
186
|
+
const wizardPollResponseSchema = z
|
|
187
|
+
.object({
|
|
188
|
+
wizardId: z.string(),
|
|
189
|
+
teamId: z.number(),
|
|
190
|
+
slug: z.string(),
|
|
191
|
+
env: z.string(),
|
|
192
|
+
status: z.string(),
|
|
193
|
+
appId: z.string().optional(),
|
|
194
|
+
oauthAuthorizeUrl: z.string().optional(),
|
|
195
|
+
error: z.string().optional(),
|
|
196
|
+
localEnv: z
|
|
197
|
+
.object({
|
|
198
|
+
botToken: z.string().min(1),
|
|
199
|
+
appToken: z.string().optional(),
|
|
200
|
+
})
|
|
201
|
+
.optional(),
|
|
202
|
+
localEnvClaimed: z.boolean().optional(),
|
|
203
|
+
})
|
|
204
|
+
.passthrough();
|
|
205
|
+
|
|
206
|
+
export interface SlackWizardMintResult {
|
|
207
|
+
wizardId: string;
|
|
208
|
+
url: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface SlackWizardPollResult {
|
|
212
|
+
wizardId: string;
|
|
213
|
+
teamId: number;
|
|
214
|
+
slug: string;
|
|
215
|
+
env: string;
|
|
216
|
+
status: string;
|
|
217
|
+
appId?: string;
|
|
218
|
+
oauthAuthorizeUrl?: string;
|
|
219
|
+
error?: string;
|
|
220
|
+
localEnv?: { botToken: string; appToken?: string };
|
|
221
|
+
localEnvClaimed?: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface SlackWizardMintInput {
|
|
225
|
+
teamId: number;
|
|
226
|
+
slug: string;
|
|
227
|
+
env: SlackAppEnv;
|
|
228
|
+
envPrefix?: string;
|
|
229
|
+
name?: string;
|
|
230
|
+
gitRepoUrl?: string;
|
|
231
|
+
gitRef?: string;
|
|
232
|
+
agentPath?: string;
|
|
233
|
+
channelPosts?: boolean;
|
|
234
|
+
slackTeamId?: string;
|
|
235
|
+
iconUrl?: string;
|
|
236
|
+
force?: boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
179
239
|
// ============================================================================
|
|
180
240
|
// Client
|
|
181
241
|
// ============================================================================
|
|
@@ -224,6 +284,27 @@ export class AgentServeSlackClient {
|
|
|
224
284
|
return connectResponseSchema.parse(raw).url;
|
|
225
285
|
}
|
|
226
286
|
|
|
287
|
+
async mintWizard(
|
|
288
|
+
input: SlackWizardMintInput
|
|
289
|
+
): Promise<SlackWizardMintResult> {
|
|
290
|
+
const raw = await this.request({
|
|
291
|
+
method: "POST",
|
|
292
|
+
path: "/internal/agent-serve/slack/wizard",
|
|
293
|
+
body: input,
|
|
294
|
+
context: { verb: "start Slack setup" },
|
|
295
|
+
});
|
|
296
|
+
return mintWizardResponseSchema.parse(raw);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async pollWizard(wizardId: string): Promise<SlackWizardPollResult> {
|
|
300
|
+
const raw = await this.request({
|
|
301
|
+
method: "GET",
|
|
302
|
+
path: `/internal/agent-serve/slack/wizard/${encodeURIComponent(wizardId)}`,
|
|
303
|
+
context: { verb: "poll Slack setup" },
|
|
304
|
+
});
|
|
305
|
+
return wizardPollResponseSchema.parse(raw);
|
|
306
|
+
}
|
|
307
|
+
|
|
227
308
|
/**
|
|
228
309
|
* Provision (create or update) and install one managed Slack app for
|
|
229
310
|
* (slackTeamId, slug, env). Expected failures throw
|