@cursor/july 0.1.97 → 0.1.99
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 +1 -1
- package/dist/bin/agent-serve.js +6 -7
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +16 -0
- package/dist/channels/origin/origin-channel.d.ts +2 -0
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +37 -4
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +76 -55
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +1 -1
- package/dist/channels/slack/live-delivery.d.ts +1 -1
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +4 -2
- package/dist/channels/slack/progress-delivery.d.ts +28 -0
- package/dist/channels/slack/progress-delivery.d.ts.map +1 -0
- package/dist/channels/slack/progress-delivery.js +115 -0
- package/dist/channels/slack/types.d.ts +9 -1
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.B3rWNYE1.js → app.BplEAZ3S.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DpuI4i7Z.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C1lhqJJR.js → VPLocalSearchBox.CHXyv4wP.js} +1 -1
- package/dist/docs/assets/chunks/{theme.B40_SXuv.js → theme.BTcsZmjQ.js} +2 -2
- package/dist/docs/assets/{reference_cli.md.RyZf5OTE.js → reference_cli.md.DfoeyvL0.js} +4 -4
- package/dist/docs/assets/{reference_cli.md.RyZf5OTE.lean.js → reference_cli.md.DfoeyvL0.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.5zOAbV86.js → reference_http-api.md.BEJx9XVj.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.B84gw9Ii.js → reference_tools.md.CvAHsdSp.js} +10 -2
- package/dist/docs/assets/{reference_tools.md.B84gw9Ii.lean.js → reference_tools.md.CvAHsdSp.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/design/runtime-abstraction.md +1757 -0
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +4 -4
- package/dist/docs/llms-full.txt +1789 -7
- package/dist/docs/llms.txt +1 -0
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +7 -7
- package/dist/docs/reference/cli.md +1 -7
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/http-api.md +1 -0
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +14 -6
- package/dist/docs/reference/tools.md +25 -0
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +4 -4
- package/dist/docs/templates/agents-md.html +4 -4
- package/dist/docs/templates/code-wiki.html +4 -4
- package/dist/docs/templates/demo.html +4 -4
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +4 -4
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/files-backends/cursor-hosted.d.ts +3 -2
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/as-of.d.ts +19 -0
- package/dist/internal/as-of.d.ts.map +1 -0
- package/dist/internal/as-of.js +82 -0
- package/dist/internal/cli-deploy.d.ts +2 -1
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +100 -5
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +5 -6
- package/dist/internal/framework-file-storage.d.ts +10 -16
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +11 -40
- package/dist/internal/framework-storage-selection.d.ts +13 -11
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +37 -26
- package/dist/internal/hosted-admission-adapter.d.ts +28 -0
- package/dist/internal/hosted-admission-adapter.d.ts.map +1 -0
- package/dist/internal/hosted-admission-adapter.js +7 -0
- package/dist/internal/hosted-delivery-protocol.d.ts +6 -1
- package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
- package/dist/internal/hosted-delivery-protocol.js +1 -1
- package/dist/internal/hosted-delivery.d.ts +15 -1
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +47 -21
- package/dist/internal/hosted-execution-diag.d.ts +11 -0
- package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
- package/dist/internal/hosted-execution-diag.js +39 -2
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +4 -1
- package/dist/internal/null-kv-storage.d.ts +48 -0
- package/dist/internal/null-kv-storage.d.ts.map +1 -0
- package/dist/internal/null-kv-storage.js +58 -0
- package/dist/internal/server.d.ts +3 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +82 -19
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +24 -4
- package/dist/internal/session-run-log.d.ts +120 -0
- package/dist/internal/session-run-log.d.ts.map +1 -0
- package/dist/internal/session-run-log.js +359 -0
- package/dist/internal/storage-coordinator.d.ts +6 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +9 -1
- package/dist/internal/tool-policy.d.ts +20 -8
- package/dist/internal/tool-policy.d.ts.map +1 -1
- package/dist/internal/tool-policy.js +11 -0
- package/dist/playground/assets/index-D7rGZNjy.css +1 -0
- package/dist/playground/assets/index-jzNbJENa.js +69 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +8 -0
- package/dist/storage-protocol.d.ts.map +1 -1
- package/dist/storage-protocol.js +8 -0
- package/dist/tools.d.ts +12 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/design/runtime-abstraction.md +1757 -0
- package/docs/reference/cli.md +1 -7
- package/docs/reference/http-api.md +1 -0
- package/docs/reference/tools.md +25 -0
- package/package.json +1 -1
- package/src/bin/agent-serve.ts +6 -7
- package/src/channels/github/github-channel.ts +17 -0
- package/src/channels/origin/origin-channel.ts +74 -10
- package/src/channels/slack/defaults.ts +91 -63
- package/src/channels/slack/dispatch.ts +2 -0
- package/src/channels/slack/live-delivery.ts +5 -3
- package/src/channels/slack/progress-delivery.ts +133 -0
- package/src/channels/slack/types.ts +9 -1
- package/src/files-backends/cursor-hosted.ts +3 -2
- package/src/index.ts +4 -0
- package/src/internal/as-of.ts +107 -0
- package/src/internal/cli-deploy.ts +128 -8
- package/src/internal/cursor-event-relay.ts +5 -6
- package/src/internal/framework-file-storage.ts +11 -45
- package/src/internal/framework-storage-selection.ts +41 -25
- package/src/internal/hosted-admission-adapter.ts +42 -0
- package/src/internal/hosted-delivery-protocol.ts +7 -2
- package/src/internal/hosted-delivery.ts +88 -36
- package/src/internal/hosted-execution-diag.ts +45 -0
- package/src/internal/http-channel.ts +8 -0
- package/src/internal/null-kv-storage.ts +74 -0
- package/src/internal/server.ts +100 -2
- package/src/internal/session-engine.ts +32 -1
- package/src/internal/session-run-log.ts +584 -0
- package/src/internal/storage-coordinator.ts +11 -1
- package/src/internal/tool-policy.ts +31 -9
- package/src/storage-protocol.ts +10 -0
- package/src/tools.ts +12 -2
- package/src/types.ts +25 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BU9nHdnQ.js +0 -1
- package/dist/playground/assets/index-DrkI6y5O.js +0 -88
- package/dist/playground/assets/index-DzNGwm7q.css +0 -1
- /package/dist/docs/assets/{reference_http-api.md.5zOAbV86.lean.js → reference_http-api.md.BEJx9XVj.lean.js} +0 -0
package/docs/reference/cli.md
CHANGED
|
@@ -594,7 +594,7 @@ commands with an explicit `--json` flag skip this automatic check.
|
|
|
594
594
|
agent-sdk deploy [--dir <path>] [--slug <slug> | --all] [--team <id>]
|
|
595
595
|
[--repo <https-url>] [--ref <git-ref>] [--path <agent-path>]
|
|
596
596
|
[--cursor-events-repo owner/name]...
|
|
597
|
-
[--allow-domain <domain>]...
|
|
597
|
+
[--allow-domain <domain>]...
|
|
598
598
|
[--no-wait] [--json]
|
|
599
599
|
```
|
|
600
600
|
|
|
@@ -622,12 +622,6 @@ be a lowercase hostname with at least two labels and an alphabetic
|
|
|
622
622
|
top-level domain. One leading `*.` wildcard is allowed. A deployment
|
|
623
623
|
can declare at most 20 domains.
|
|
624
624
|
|
|
625
|
-
`--multi-tenant` adds a release to an existing Cursor-managed
|
|
626
|
-
(multi-tenant) product such as `security-reviewer`. It is valid only
|
|
627
|
-
for `architecture: "v2"` and cannot create a new multi-tenant
|
|
628
|
-
application — those stay on the operator seed path. Without the flag,
|
|
629
|
-
HTTP deploy stays single-tenant and a managed slug 409s.
|
|
630
|
-
|
|
631
625
|
By default, the command polls every three seconds for up to ten minutes
|
|
632
626
|
and succeeds only when the deployment reaches `running`. `--no-wait`
|
|
633
627
|
returns after the deployment request is accepted. Multi-agent deploys
|
|
@@ -53,6 +53,7 @@ and `playgroundUrl` deep-links the session in the playground.
|
|
|
53
53
|
| `message` | Required user message |
|
|
54
54
|
| `title` | Session title |
|
|
55
55
|
| `dryRun` | Run read tools and stub write tools |
|
|
56
|
+
| `asOf` | ISO-8601 instant with a timezone, frozen at create; the prompt states it, `ctx.now()` returns it, and tools declaring `timeArgs` refuse calls not bounded at or before it. `400` when unusable |
|
|
56
57
|
| `workspaceFiles` | UTF-8 files written into the session workspace |
|
|
57
58
|
| `cloud` | Per-session cloud options merged over the agent defaults |
|
|
58
59
|
|
package/docs/reference/tools.md
CHANGED
|
@@ -147,6 +147,31 @@ export default defineTool({
|
|
|
147
147
|
`dryRunResult` keeps the result shape stable. Without it, a stubbed write
|
|
148
148
|
returns `"Operation acknowledged."`. Traces mark the result as stubbed.
|
|
149
149
|
|
|
150
|
+
### Bound time arguments under `asOf`
|
|
151
|
+
|
|
152
|
+
Declare `timeArgs` with the input properties that carry time bounds. In a
|
|
153
|
+
session created with `asOf`, every named argument must be an absolute
|
|
154
|
+
ISO-8601 instant with a timezone, no later than the session's frozen
|
|
155
|
+
instant. A call that omits one, or supplies a relative or later value, is
|
|
156
|
+
refused with an error telling the model to retry with absolute bounds.
|
|
157
|
+
Sessions without `asOf` ignore the declaration.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
export default defineTool({
|
|
161
|
+
description: "Query telemetry between two instants.",
|
|
162
|
+
inputSchema: z.object({ since: z.string(), until: z.string() }),
|
|
163
|
+
effect: "read",
|
|
164
|
+
timeArgs: ["since", "until"],
|
|
165
|
+
async execute({ since, until }) {
|
|
166
|
+
return queryTelemetry(since, until);
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Every name in `timeArgs` must exist in the input schema; a session with
|
|
172
|
+
`asOf` refuses to start otherwise, because a misspelled name would silently
|
|
173
|
+
check nothing.
|
|
174
|
+
|
|
150
175
|
## Define an agent tool
|
|
151
176
|
|
|
152
177
|
Set `execution: "agent"` and the tool materializes as a shell script
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cursor/july",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.99",
|
|
4
4
|
"description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
package/src/bin/agent-serve.ts
CHANGED
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
isServeRestartWorker,
|
|
72
72
|
runEnterRestartSupervisor,
|
|
73
73
|
} from "../internal/serve-restart.js";
|
|
74
|
+
import { flushSessionRunFatal } from "../internal/session-run-log.js";
|
|
74
75
|
import { clearTerminal, stdoutPalette } from "../internal/terminal-style.js";
|
|
75
76
|
import { cmdUpdate, warnIfOutdated } from "../internal/update-check.js";
|
|
76
77
|
import type { AgentProject, AgentServeHandle } from "../types.js";
|
|
@@ -140,7 +141,7 @@ Usage:
|
|
|
140
141
|
${CLI} deploy [--dir <path>] [--slug <slug> | --all] [--team <id>] [--json]
|
|
141
142
|
[--no-wait] [--repo <url>] [--ref <ref>]
|
|
142
143
|
[--path <agentPath>] [--cursor-events-repo <owner/name>]...
|
|
143
|
-
[--allow-domain <domain>]...
|
|
144
|
+
[--allow-domain <domain>]...
|
|
144
145
|
${CLI} manifest [--dir <path>] [--json]
|
|
145
146
|
${CLI} deployments [--team <id>] [--json]
|
|
146
147
|
${CLI} deployment <slug> [--team <id>] [--json]
|
|
@@ -268,8 +269,6 @@ Commands:
|
|
|
268
269
|
(flags always win). When --dir is a multi-agent parent,
|
|
269
270
|
pick one interactively, or pass --slug / --all. First deploy
|
|
270
271
|
of a slug prints the alias token exactly once — save it.
|
|
271
|
-
--multi-tenant adds a release to an existing Cursor-managed
|
|
272
|
-
product (v2 only); it cannot create a new multi-tenant app.
|
|
273
272
|
deployments List the team's deployments (slug, status, generation, kind).
|
|
274
273
|
deployment Show one deployment in full (status, lastError, engine info).
|
|
275
274
|
stop Stop a deployment and wait until it reports stopped.
|
|
@@ -432,9 +431,6 @@ Options:
|
|
|
432
431
|
--allow-domain deploy: egress domain the engine pod may reach
|
|
433
432
|
(repeatable; unioned with hosting.egressDomains from the
|
|
434
433
|
agent manifest). Repo-backed deploys only.
|
|
435
|
-
--multi-tenant deploy: add a release to an existing Cursor-managed
|
|
436
|
-
(multi-tenant) product. architecture v2 only. Cannot
|
|
437
|
-
create a new multi-tenant application.
|
|
438
434
|
--from-argv secrets set: allow NAME=VALUE on argv (leaks into shell
|
|
439
435
|
history and agent-captured terminals). Default is names
|
|
440
436
|
only with a hidden prompt or piped stdin.
|
|
@@ -578,6 +574,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
578
574
|
all: { type: "boolean", default: false },
|
|
579
575
|
"cursor-events-repo": { type: "string", multiple: true },
|
|
580
576
|
"allow-domain": { type: "string", multiple: true },
|
|
577
|
+
// Hidden Anysphere-only flag; omitted from help.
|
|
581
578
|
"multi-tenant": { type: "boolean", default: false },
|
|
582
579
|
"from-argv": { type: "boolean", default: false },
|
|
583
580
|
strict: { type: "boolean", default: false },
|
|
@@ -1729,7 +1726,9 @@ function installTransportErrorGuards(): void {
|
|
|
1729
1726
|
process.stderr.write(
|
|
1730
1727
|
`[agent-sdk] uncaughtException: ${error.stack ?? error.message}\n`
|
|
1731
1728
|
);
|
|
1732
|
-
|
|
1729
|
+
void flushSessionRunFatal(error).finally(() => {
|
|
1730
|
+
process.exit(1);
|
|
1731
|
+
});
|
|
1733
1732
|
});
|
|
1734
1733
|
}
|
|
1735
1734
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../../internal/continuation-identity.js";
|
|
14
14
|
import { isDeferredChannelSession } from "../../internal/deferred-channel-session.js";
|
|
15
15
|
import { isDevMode } from "../../internal/dev-mode.js";
|
|
16
|
+
import { attachHostedAdmissionAdapter } from "../../internal/hosted-admission-adapter.js";
|
|
16
17
|
import { bindLocalIngressSend } from "../../internal/local-ingress.js";
|
|
17
18
|
import type {
|
|
18
19
|
AgentCloudOptions,
|
|
@@ -804,6 +805,22 @@ export function githubChannel(
|
|
|
804
805
|
],
|
|
805
806
|
});
|
|
806
807
|
githubChannelOptionsByDefinition.set(definition, channelOptions);
|
|
808
|
+
attachHostedAdmissionAdapter(definition, {
|
|
809
|
+
kind: GITHUB_CHANNEL_KIND,
|
|
810
|
+
prepareArgs: withPropagatingGitHubBackgroundErrors,
|
|
811
|
+
dispatch: ({ eventType, payload, deliveryId, args }) =>
|
|
812
|
+
dispatchGitHubWebhook({
|
|
813
|
+
options: channelOptions,
|
|
814
|
+
eventName: eventType,
|
|
815
|
+
payload,
|
|
816
|
+
deliveryId,
|
|
817
|
+
args,
|
|
818
|
+
}),
|
|
819
|
+
getOutcome: (response) => {
|
|
820
|
+
const outcome = getGitHubDispatchOutcome(response);
|
|
821
|
+
return outcome === "skip" || outcome === "task" ? outcome : undefined;
|
|
822
|
+
},
|
|
823
|
+
});
|
|
807
824
|
if (cursorCredentials !== undefined) {
|
|
808
825
|
registerGithubCursorAccountRuntime(definition, {
|
|
809
826
|
options,
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
continuationSource,
|
|
11
11
|
normalizeIdentity,
|
|
12
12
|
} from "../../internal/continuation-identity.js";
|
|
13
|
+
import { isDeferredChannelSession } from "../../internal/deferred-channel-session.js";
|
|
13
14
|
import { isDevMode } from "../../internal/dev-mode.js";
|
|
15
|
+
import { attachHostedAdmissionAdapter } from "../../internal/hosted-admission-adapter.js";
|
|
14
16
|
import { bindLocalIngressSend } from "../../internal/local-ingress.js";
|
|
15
17
|
import type {
|
|
16
18
|
AgentCloudOptions,
|
|
@@ -441,11 +443,35 @@ function enrichEventContext(input: {
|
|
|
441
443
|
};
|
|
442
444
|
}
|
|
443
445
|
|
|
446
|
+
export type OriginDispatchOutcome = "skip" | "send" | "task";
|
|
447
|
+
|
|
448
|
+
const dispatchOutcomeByResponse = new WeakMap<
|
|
449
|
+
Response,
|
|
450
|
+
OriginDispatchOutcome
|
|
451
|
+
>();
|
|
452
|
+
|
|
453
|
+
function withDispatchOutcome(
|
|
454
|
+
response: Response,
|
|
455
|
+
outcome: OriginDispatchOutcome
|
|
456
|
+
): Response {
|
|
457
|
+
dispatchOutcomeByResponse.set(response, outcome);
|
|
458
|
+
return response;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function getOriginDispatchOutcome(
|
|
462
|
+
response: Response
|
|
463
|
+
): OriginDispatchOutcome | undefined {
|
|
464
|
+
return dispatchOutcomeByResponse.get(response);
|
|
465
|
+
}
|
|
466
|
+
|
|
444
467
|
function skipped(
|
|
445
468
|
reason: string,
|
|
446
469
|
extra: Record<string, unknown> = {}
|
|
447
470
|
): Response {
|
|
448
|
-
return
|
|
471
|
+
return withDispatchOutcome(
|
|
472
|
+
Response.json({ ok: true, skipped: true, reason, ...extra }),
|
|
473
|
+
"skip"
|
|
474
|
+
);
|
|
449
475
|
}
|
|
450
476
|
|
|
451
477
|
function acknowledgeTask(
|
|
@@ -462,9 +488,12 @@ function acknowledgeTask(
|
|
|
462
488
|
});
|
|
463
489
|
})
|
|
464
490
|
);
|
|
465
|
-
return
|
|
466
|
-
|
|
467
|
-
|
|
491
|
+
return withDispatchOutcome(
|
|
492
|
+
Response.json(
|
|
493
|
+
{ ok: true, dispatched: true, mode: "task", ...extra },
|
|
494
|
+
{ status: 202 }
|
|
495
|
+
),
|
|
496
|
+
"task"
|
|
468
497
|
);
|
|
469
498
|
}
|
|
470
499
|
|
|
@@ -485,12 +514,26 @@ async function startTurn(input: {
|
|
|
485
514
|
title: input.dispatch.title ?? defaultOriginTitle(input.ctx),
|
|
486
515
|
...(input.cloud === undefined ? {} : { cloud: input.cloud }),
|
|
487
516
|
});
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
517
|
+
if (isDeferredChannelSession(session)) {
|
|
518
|
+
// Hosted admission captured the send; the control plane allocates the
|
|
519
|
+
// session after acknowledgement, so there is nothing to report yet.
|
|
520
|
+
return withDispatchOutcome(
|
|
521
|
+
Response.json(
|
|
522
|
+
{ ok: true, dispatched: true, mode: "turn" },
|
|
523
|
+
{ status: 202 }
|
|
524
|
+
),
|
|
525
|
+
"send"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return withDispatchOutcome(
|
|
529
|
+
Response.json({
|
|
530
|
+
ok: true,
|
|
531
|
+
sessionId: session.id,
|
|
532
|
+
isNew: session.isNew,
|
|
533
|
+
continuationToken: session.continuationToken,
|
|
534
|
+
}),
|
|
535
|
+
"send"
|
|
536
|
+
);
|
|
494
537
|
}
|
|
495
538
|
|
|
496
539
|
async function resolveCloudOptions(
|
|
@@ -812,6 +855,27 @@ export function originChannel(
|
|
|
812
855
|
],
|
|
813
856
|
});
|
|
814
857
|
originChannelOptionsByDefinition.set(definition, channelOptions);
|
|
858
|
+
attachHostedAdmissionAdapter(definition, {
|
|
859
|
+
kind: ORIGIN_CHANNEL_KIND,
|
|
860
|
+
dispatch: async ({ payload, args }) => {
|
|
861
|
+
const envelope = parseOriginWebhookEnvelope(payload);
|
|
862
|
+
if (envelope === null) {
|
|
863
|
+
return Response.json(
|
|
864
|
+
{ ok: false, error: "invalid_origin_payload" },
|
|
865
|
+
{ status: 400 }
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
return dispatchOriginWebhook({
|
|
869
|
+
options: channelOptions,
|
|
870
|
+
envelope,
|
|
871
|
+
args,
|
|
872
|
+
});
|
|
873
|
+
},
|
|
874
|
+
getOutcome: (response) => {
|
|
875
|
+
const outcome = getOriginDispatchOutcome(response);
|
|
876
|
+
return outcome === "skip" || outcome === "task" ? outcome : undefined;
|
|
877
|
+
},
|
|
878
|
+
});
|
|
815
879
|
if (usesCursorAccount) {
|
|
816
880
|
registerOriginCursorAccountRuntime(definition, {
|
|
817
881
|
options: channelOptions,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./constants.js";
|
|
19
19
|
import { createStreamDeliveryStrategy } from "./live-delivery.js";
|
|
20
20
|
import { defaultSlackLog, type SlackLog } from "./log.js";
|
|
21
|
+
import { createProgressDelivery } from "./progress-delivery.js";
|
|
21
22
|
import { buildTaskChunkForResult, buildTaskChunksForCalls } from "./stream.js";
|
|
22
23
|
import type {
|
|
23
24
|
SlackAccessPolicy,
|
|
@@ -154,6 +155,10 @@ export function buildDefaultEvents(
|
|
|
154
155
|
const botToken = credentials?.botToken;
|
|
155
156
|
const getLog = options.getLog ?? (() => defaultSlackLog);
|
|
156
157
|
const strategy = createStreamDeliveryStrategy({ botToken, getLog });
|
|
158
|
+
const progress = createProgressDelivery({ botToken, getLog });
|
|
159
|
+
|
|
160
|
+
const streamLive = (state: SlackChannelState): boolean =>
|
|
161
|
+
streaming && !state.streamingUnavailable;
|
|
157
162
|
|
|
158
163
|
const clearWorkingStatus = async (
|
|
159
164
|
channelId: string,
|
|
@@ -174,6 +179,62 @@ export function buildDefaultEvents(
|
|
|
174
179
|
}
|
|
175
180
|
};
|
|
176
181
|
|
|
182
|
+
const finishProgress = async (args: {
|
|
183
|
+
channel: ChannelEventContext<SlackChannelState>;
|
|
184
|
+
text: string;
|
|
185
|
+
sessionId: string;
|
|
186
|
+
}): Promise<boolean> => {
|
|
187
|
+
const log = getLog();
|
|
188
|
+
const state = args.channel.state;
|
|
189
|
+
if (
|
|
190
|
+
state.channelId === null ||
|
|
191
|
+
state.threadTs === null ||
|
|
192
|
+
args.text === ""
|
|
193
|
+
) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
if (await progress.replace({ channel: args.channel, text: args.text })) {
|
|
197
|
+
log.info("reply delivered via progress update", {
|
|
198
|
+
channel: state.channelId,
|
|
199
|
+
thread_ts: state.threadTs,
|
|
200
|
+
session: args.sessionId,
|
|
201
|
+
chars: args.text.length,
|
|
202
|
+
});
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
await progress.clear({ channel: args.channel });
|
|
206
|
+
try {
|
|
207
|
+
const posted = await postMessage({
|
|
208
|
+
botToken,
|
|
209
|
+
channel: state.channelId,
|
|
210
|
+
text: args.text,
|
|
211
|
+
threadTs: state.threadTs,
|
|
212
|
+
});
|
|
213
|
+
state.lastPostedText = args.text;
|
|
214
|
+
state.lastBotMessageTs = posted.ts;
|
|
215
|
+
log.info("reply delivered via postMessage", {
|
|
216
|
+
channel: state.channelId,
|
|
217
|
+
thread_ts: state.threadTs,
|
|
218
|
+
message_ts: posted.ts,
|
|
219
|
+
session: args.sessionId,
|
|
220
|
+
chars: args.text.length,
|
|
221
|
+
});
|
|
222
|
+
return true;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
log.error(
|
|
225
|
+
`postMessage failed: ${
|
|
226
|
+
error instanceof Error ? error.message : String(error)
|
|
227
|
+
}`,
|
|
228
|
+
{
|
|
229
|
+
channel: state.channelId,
|
|
230
|
+
thread_ts: state.threadTs,
|
|
231
|
+
session: args.sessionId,
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
177
238
|
/**
|
|
178
239
|
* Deliver assistant text to the bound Slack thread. Used by a stop-reason
|
|
179
240
|
* `message.completed` and by `turn.completed` when the last assistant
|
|
@@ -185,7 +246,6 @@ export function buildDefaultEvents(
|
|
|
185
246
|
text: string;
|
|
186
247
|
sessionId: string;
|
|
187
248
|
}): Promise<boolean> => {
|
|
188
|
-
const log = getLog();
|
|
189
249
|
const state = args.channel.state;
|
|
190
250
|
if (
|
|
191
251
|
state.channelId === null ||
|
|
@@ -199,8 +259,7 @@ export function buildDefaultEvents(
|
|
|
199
259
|
// pending. Treat a match as "already on Slack", not "already finalized".
|
|
200
260
|
const streamTs = state.streamTs;
|
|
201
261
|
if (streamTs !== null && streamTs !== "") {
|
|
202
|
-
|
|
203
|
-
if (canStream) {
|
|
262
|
+
if (streamLive(state)) {
|
|
204
263
|
await strategy.finalize({
|
|
205
264
|
channel: args.channel,
|
|
206
265
|
channelId: state.streamChannelId ?? state.channelId,
|
|
@@ -214,8 +273,7 @@ export function buildDefaultEvents(
|
|
|
214
273
|
return true;
|
|
215
274
|
}
|
|
216
275
|
|
|
217
|
-
|
|
218
|
-
if (canStream) {
|
|
276
|
+
if (streamLive(state)) {
|
|
219
277
|
const { delivered } = await strategy.finalize({
|
|
220
278
|
channel: args.channel,
|
|
221
279
|
channelId: state.streamChannelId ?? state.channelId,
|
|
@@ -227,47 +285,20 @@ export function buildDefaultEvents(
|
|
|
227
285
|
}
|
|
228
286
|
}
|
|
229
287
|
|
|
230
|
-
|
|
231
|
-
const posted = await postMessage({
|
|
232
|
-
botToken,
|
|
233
|
-
channel: state.channelId,
|
|
234
|
-
text: args.text,
|
|
235
|
-
threadTs: state.threadTs,
|
|
236
|
-
});
|
|
237
|
-
state.lastPostedText = args.text;
|
|
238
|
-
state.lastBotMessageTs = posted.ts;
|
|
239
|
-
log.info("reply delivered via postMessage", {
|
|
240
|
-
channel: state.channelId,
|
|
241
|
-
thread_ts: state.threadTs,
|
|
242
|
-
message_ts: posted.ts,
|
|
243
|
-
session: args.sessionId,
|
|
244
|
-
chars: args.text.length,
|
|
245
|
-
});
|
|
246
|
-
return true;
|
|
247
|
-
} catch (error) {
|
|
248
|
-
log.error(
|
|
249
|
-
`postMessage failed: ${
|
|
250
|
-
error instanceof Error ? error.message : String(error)
|
|
251
|
-
}`,
|
|
252
|
-
{
|
|
253
|
-
channel: state.channelId,
|
|
254
|
-
thread_ts: state.threadTs,
|
|
255
|
-
session: args.sessionId,
|
|
256
|
-
}
|
|
257
|
-
);
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
288
|
+
return finishProgress(args);
|
|
260
289
|
};
|
|
261
290
|
|
|
262
291
|
return {
|
|
263
292
|
async "turn.started"(event, channel) {
|
|
264
293
|
const log = getLog();
|
|
294
|
+
await progress.clear({ channel });
|
|
265
295
|
channel.state.pendingToolCallMessage = null;
|
|
266
296
|
channel.state.lastReasoningTypingAtMs = null;
|
|
267
297
|
channel.state.lastReasoningTypingStatus = null;
|
|
268
298
|
channel.state.streamTs = null;
|
|
269
299
|
channel.state.streamChannelId = null;
|
|
270
300
|
channel.state.lastPostedText = null;
|
|
301
|
+
channel.state.streamBlockText = null;
|
|
271
302
|
// Keep lastBotMessageTs across turns — warm delta watermark.
|
|
272
303
|
channel.state.approvalMessageByCallId = {};
|
|
273
304
|
|
|
@@ -293,7 +324,7 @@ export function buildDefaultEvents(
|
|
|
293
324
|
},
|
|
294
325
|
|
|
295
326
|
async "message.appended"(event, channel) {
|
|
296
|
-
if (!
|
|
327
|
+
if (!streamLive(channel.state)) {
|
|
297
328
|
return;
|
|
298
329
|
}
|
|
299
330
|
const channelId =
|
|
@@ -362,8 +393,7 @@ export function buildDefaultEvents(
|
|
|
362
393
|
if (
|
|
363
394
|
strategy.inlineToolSteps &&
|
|
364
395
|
thinkingSteps &&
|
|
365
|
-
|
|
366
|
-
channel.state.streamingUnavailable !== true
|
|
396
|
+
streamLive(channel.state)
|
|
367
397
|
) {
|
|
368
398
|
const channelId =
|
|
369
399
|
channel.state.streamChannelId ?? channel.state.channelId;
|
|
@@ -403,17 +433,24 @@ export function buildDefaultEvents(
|
|
|
403
433
|
threadTs: channel.state.threadTs,
|
|
404
434
|
status: statusText,
|
|
405
435
|
});
|
|
436
|
+
if (
|
|
437
|
+
thinkingSteps &&
|
|
438
|
+
channel.state.streamingUnavailable === true &&
|
|
439
|
+
(channel.state.streamTs === null || channel.state.streamTs === "")
|
|
440
|
+
) {
|
|
441
|
+
await progress.upsert({ channel, text: statusText });
|
|
442
|
+
}
|
|
406
443
|
}
|
|
407
444
|
},
|
|
408
445
|
|
|
409
446
|
async "action.result"(event, channel) {
|
|
447
|
+
const streamTs = channel.state.streamTs;
|
|
410
448
|
if (
|
|
411
449
|
!strategy.inlineToolSteps ||
|
|
412
450
|
!thinkingSteps ||
|
|
413
|
-
!
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
channel.state.streamTs === ""
|
|
451
|
+
!streamLive(channel.state) ||
|
|
452
|
+
streamTs === null ||
|
|
453
|
+
streamTs === ""
|
|
417
454
|
) {
|
|
418
455
|
return;
|
|
419
456
|
}
|
|
@@ -430,7 +467,7 @@ export function buildDefaultEvents(
|
|
|
430
467
|
await appendChatStream({
|
|
431
468
|
botToken,
|
|
432
469
|
channel: channelId,
|
|
433
|
-
ts:
|
|
470
|
+
ts: streamTs,
|
|
434
471
|
chunks: [chunk],
|
|
435
472
|
});
|
|
436
473
|
},
|
|
@@ -439,6 +476,9 @@ export function buildDefaultEvents(
|
|
|
439
476
|
if (event.data.finishReason === "tool_call") {
|
|
440
477
|
channel.state.pendingToolCallMessage =
|
|
441
478
|
firstNonEmptyLine(event.data.text) ?? null;
|
|
479
|
+
// The harness restarts its cumulative text after a tool call, so the
|
|
480
|
+
// next block appends in full instead of diffing against this one.
|
|
481
|
+
channel.state.streamBlockText = null;
|
|
442
482
|
return;
|
|
443
483
|
}
|
|
444
484
|
channel.state.pendingToolCallMessage = null;
|
|
@@ -452,6 +492,7 @@ export function buildDefaultEvents(
|
|
|
452
492
|
// and clear typing.
|
|
453
493
|
if (event.data.text === "") {
|
|
454
494
|
await strategy.finalizeEmpty({ channel });
|
|
495
|
+
await progress.clear({ channel });
|
|
455
496
|
await clearWorkingStatus(state.channelId, state.threadTs);
|
|
456
497
|
return;
|
|
457
498
|
}
|
|
@@ -492,6 +533,7 @@ export function buildDefaultEvents(
|
|
|
492
533
|
// Always close leftovers. deliverReplyText may fall through to
|
|
493
534
|
// postMessage when streaming is unavailable and leave streamTs open.
|
|
494
535
|
await strategy.cleanupOrphan({ channel });
|
|
536
|
+
await progress.clear({ channel });
|
|
495
537
|
|
|
496
538
|
// Always drop the assistant chip. message.completed already clears
|
|
497
539
|
// after a normal reply, but a tool-call-only finish (or a missed
|
|
@@ -539,7 +581,7 @@ export function buildDefaultEvents(
|
|
|
539
581
|
const errorText = formatSlackTurnFailureMessage(event.data.message);
|
|
540
582
|
|
|
541
583
|
let delivered = false;
|
|
542
|
-
if (
|
|
584
|
+
if (streamLive(state)) {
|
|
543
585
|
({ delivered } = await strategy.fail({
|
|
544
586
|
channel,
|
|
545
587
|
channelId: state.streamChannelId ?? state.channelId,
|
|
@@ -548,25 +590,11 @@ export function buildDefaultEvents(
|
|
|
548
590
|
}));
|
|
549
591
|
}
|
|
550
592
|
if (!delivered) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
threadTs: state.threadTs,
|
|
557
|
-
});
|
|
558
|
-
} catch (error) {
|
|
559
|
-
log.error(
|
|
560
|
-
`turn.failed postMessage failed: ${
|
|
561
|
-
error instanceof Error ? error.message : String(error)
|
|
562
|
-
}`,
|
|
563
|
-
{
|
|
564
|
-
channel: state.channelId,
|
|
565
|
-
thread_ts: state.threadTs,
|
|
566
|
-
session: event.sessionId,
|
|
567
|
-
}
|
|
568
|
-
);
|
|
569
|
-
}
|
|
593
|
+
await finishProgress({
|
|
594
|
+
channel,
|
|
595
|
+
text: errorText,
|
|
596
|
+
sessionId: event.sessionId,
|
|
597
|
+
});
|
|
570
598
|
}
|
|
571
599
|
await clearWorkingStatus(state.channelId, state.threadTs);
|
|
572
600
|
},
|
|
@@ -126,8 +126,10 @@ export function emptySlackChannelState(
|
|
|
126
126
|
streamTs: null,
|
|
127
127
|
streamChannelId: null,
|
|
128
128
|
lastPostedText: null,
|
|
129
|
+
streamBlockText: null,
|
|
129
130
|
lastBotMessageTs: null,
|
|
130
131
|
streamingUnavailable: false,
|
|
132
|
+
progressTs: null,
|
|
131
133
|
approvalMessageByCallId: {},
|
|
132
134
|
...patch,
|
|
133
135
|
};
|
|
@@ -41,7 +41,7 @@ type StreamArgs = {
|
|
|
41
41
|
/**
|
|
42
42
|
* How the default handlers deliver an in-progress reply. Methods write live
|
|
43
43
|
* delivery state (`streamTs`, `streamChannelId`, `lastPostedText`,
|
|
44
|
-
* `streamingUnavailable`) on `channel.state` directly.
|
|
44
|
+
* `streamBlockText`, `streamingUnavailable`) on `channel.state` directly.
|
|
45
45
|
*/
|
|
46
46
|
export interface LiveDeliveryStrategy {
|
|
47
47
|
readonly inlineToolSteps: boolean;
|
|
@@ -127,6 +127,7 @@ export function createStreamDeliveryStrategy(
|
|
|
127
127
|
channel.state.streamTs = started.ts;
|
|
128
128
|
channel.state.streamChannelId = started.channel;
|
|
129
129
|
channel.state.lastPostedText = hasMarkdown ? (markdown_text ?? "") : "";
|
|
130
|
+
channel.state.streamBlockText = channel.state.lastPostedText;
|
|
130
131
|
log.info("chat.startStream ok", {
|
|
131
132
|
channel: started.channel,
|
|
132
133
|
stream_ts: started.ts,
|
|
@@ -167,7 +168,7 @@ export function createStreamDeliveryStrategy(
|
|
|
167
168
|
text: string;
|
|
168
169
|
}): Promise<{ ok: boolean; error?: string }> => {
|
|
169
170
|
const appendText = computeStreamAppend(
|
|
170
|
-
args.channel.state.
|
|
171
|
+
args.channel.state.streamBlockText,
|
|
171
172
|
args.text
|
|
172
173
|
);
|
|
173
174
|
const stopped = await stopChatStream({
|
|
@@ -213,7 +214,7 @@ export function createStreamDeliveryStrategy(
|
|
|
213
214
|
return;
|
|
214
215
|
}
|
|
215
216
|
const appendText = computeStreamAppend(
|
|
216
|
-
channel.state.
|
|
217
|
+
channel.state.streamBlockText,
|
|
217
218
|
text
|
|
218
219
|
);
|
|
219
220
|
if (appendText === "") {
|
|
@@ -238,6 +239,7 @@ export function createStreamDeliveryStrategy(
|
|
|
238
239
|
return;
|
|
239
240
|
}
|
|
240
241
|
channel.state.lastPostedText = text;
|
|
242
|
+
channel.state.streamBlockText = text;
|
|
241
243
|
},
|
|
242
244
|
|
|
243
245
|
async appendChunks({ channel, channelId, chunks, sessionId }) {
|