@gajae-code/coding-agent 0.16.3 → 0.16.6
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/CHANGELOG.md +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +26 -1
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +12 -12
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +11 -8
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { processIncarnation } from "./process-incarnation";
|
|
3
4
|
|
|
4
5
|
export interface BrokerStartupFailureMarker {
|
|
5
|
-
version:
|
|
6
|
+
version: 2;
|
|
6
7
|
reason: string;
|
|
7
8
|
exitCode: number | null;
|
|
8
9
|
signal: string | null;
|
|
@@ -16,6 +17,8 @@ export interface BrokerStartupFailureMarker {
|
|
|
16
17
|
* failure.
|
|
17
18
|
*/
|
|
18
19
|
pid: number;
|
|
20
|
+
/** Kernel-derived identity for the exact process incarnation that wrote the marker. */
|
|
21
|
+
incarnation: string;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const BROKER_STARTUP_FAILURE_FILE = "broker.startup-failure.json";
|
|
@@ -30,14 +33,16 @@ function boundedMarker(
|
|
|
30
33
|
exitCode: number | null,
|
|
31
34
|
signal: string | null,
|
|
32
35
|
pid: number,
|
|
36
|
+
incarnation: string,
|
|
33
37
|
): BrokerStartupFailureMarker {
|
|
34
38
|
return {
|
|
35
|
-
version:
|
|
39
|
+
version: 2,
|
|
36
40
|
reason: reason.slice(0, MAX_BROKER_STARTUP_FAILURE_REASON),
|
|
37
41
|
exitCode,
|
|
38
42
|
signal,
|
|
39
43
|
writtenAt: Date.now(),
|
|
40
44
|
pid,
|
|
45
|
+
incarnation,
|
|
41
46
|
};
|
|
42
47
|
}
|
|
43
48
|
|
|
@@ -49,13 +54,15 @@ function boundedMarker(
|
|
|
49
54
|
*/
|
|
50
55
|
export async function writeBrokerStartupFailureMarker(
|
|
51
56
|
agentDir: string,
|
|
52
|
-
failure: { reason: string; exitCode: number | null; signal: string | null; pid: number },
|
|
57
|
+
failure: { reason: string; exitCode: number | null; signal: string | null; pid: number; incarnation?: string },
|
|
53
58
|
): Promise<void> {
|
|
54
59
|
try {
|
|
60
|
+
const incarnation = failure.incarnation ?? processIncarnation(failure.pid);
|
|
61
|
+
if (!incarnation) return;
|
|
55
62
|
await fs.mkdir(path.dirname(brokerStartupFailurePath(agentDir)), { recursive: true, mode: 0o700 });
|
|
56
63
|
await Bun.write(
|
|
57
64
|
brokerStartupFailurePath(agentDir),
|
|
58
|
-
JSON.stringify(boundedMarker(failure.reason, failure.exitCode, failure.signal, failure.pid)),
|
|
65
|
+
JSON.stringify(boundedMarker(failure.reason, failure.exitCode, failure.signal, failure.pid, incarnation)),
|
|
59
66
|
);
|
|
60
67
|
} catch {
|
|
61
68
|
// Best-effort only.
|
|
@@ -74,7 +81,7 @@ export async function readBrokerStartupFailureMarker(
|
|
|
74
81
|
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
75
82
|
const marker = value as Partial<BrokerStartupFailureMarker>;
|
|
76
83
|
if (
|
|
77
|
-
marker.version !==
|
|
84
|
+
marker.version !== 2 ||
|
|
78
85
|
typeof marker.reason !== "string" ||
|
|
79
86
|
marker.reason.length === 0 ||
|
|
80
87
|
(marker.exitCode !== null && typeof marker.exitCode !== "number") ||
|
|
@@ -82,7 +89,9 @@ export async function readBrokerStartupFailureMarker(
|
|
|
82
89
|
typeof marker.writtenAt !== "number" ||
|
|
83
90
|
typeof marker.pid !== "number" ||
|
|
84
91
|
!Number.isInteger(marker.pid) ||
|
|
85
|
-
marker.pid <= 0
|
|
92
|
+
marker.pid <= 0 ||
|
|
93
|
+
typeof marker.incarnation !== "string" ||
|
|
94
|
+
marker.incarnation.length === 0
|
|
86
95
|
)
|
|
87
96
|
return undefined;
|
|
88
97
|
return marker as BrokerStartupFailureMarker;
|
|
@@ -133,10 +133,12 @@ export type ChatDaemonAction = "stop" | "reload";
|
|
|
133
133
|
* Discord generation 72 / Slack generation 75 serialize same-generation
|
|
134
134
|
* successor attachment admission behind predecessor provider retirement, so a
|
|
135
135
|
* pre-upgrade owner cannot retain the queue race fixed by #5120.
|
|
136
|
+
* Slack generation 80 persists and fences the inbound SDK dispatch boundary so
|
|
137
|
+
* crashes, recovery, and attachment retirement cannot replay ambiguous work.
|
|
136
138
|
*/
|
|
137
139
|
export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
|
|
138
140
|
discord: 73,
|
|
139
|
-
slack:
|
|
141
|
+
slack: 80,
|
|
140
142
|
};
|
|
141
143
|
|
|
142
144
|
export function chatDaemonGeneration(kind: ChatDaemonKind): number {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { logger } from "@gajae-code/utils";
|
|
3
|
-
import { SdkClientError } from "../client/client";
|
|
3
|
+
import { SdkClientError, SdkPreparedDispatchError } from "../client/client";
|
|
4
4
|
import { SESSION_PREPARED_EVENT } from "../host/host";
|
|
5
5
|
|
|
6
6
|
import type { SessionRouterDeps } from "../router";
|
|
@@ -50,6 +50,7 @@ function chatDeliveryPhase(error: unknown): ChatDeliveryPhase | undefined {
|
|
|
50
50
|
if (error instanceof SessionRouterError) return error.phase;
|
|
51
51
|
|
|
52
52
|
if (error instanceof ChatDeliveryError) return error.phase;
|
|
53
|
+
if (error instanceof SdkPreparedDispatchError) return "pre_send";
|
|
53
54
|
if (!(error instanceof SdkClientError)) return undefined;
|
|
54
55
|
// `uncertain_after_send` is the one code that states outright that the frame
|
|
55
56
|
// reached the host: reporting it as a definite failure would tell the operator a
|
|
@@ -92,6 +93,36 @@ function isReservedIdentity(name: string | undefined): boolean {
|
|
|
92
93
|
return isLifecycleEvent(name) || isControlPlaneFrameType(name);
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Chat notifications in lean mode are an answer surface, not a mirror of the
|
|
98
|
+
* session protocol. Lifecycle, identity, and activity frames remain available
|
|
99
|
+
* to verbose consumers, while finalized answers and action-needed events are
|
|
100
|
+
* delivered in lean mode.
|
|
101
|
+
*/
|
|
102
|
+
export function shouldPublishChatFrame(
|
|
103
|
+
frame: Record<string, unknown>,
|
|
104
|
+
verbosity: "lean" | "verbose" | undefined,
|
|
105
|
+
): boolean {
|
|
106
|
+
if (verbosity !== "lean") return frame.type !== "turn_stream" || frame.phase !== "live";
|
|
107
|
+
return frame.type === "turn_stream" && frame.phase === "finalized" && frame.finalAnswer === true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A final answer's message ref identifies its one rendered message even when
|
|
112
|
+
* the SDK transport delivers an unpositioned duplicate frame.
|
|
113
|
+
*/
|
|
114
|
+
export function publicationIdForFinalChatAnswer(sessionId: string, frame: Record<string, unknown>): string | undefined {
|
|
115
|
+
if (
|
|
116
|
+
frame.type !== "turn_stream" ||
|
|
117
|
+
frame.phase !== "finalized" ||
|
|
118
|
+
frame.finalAnswer !== true ||
|
|
119
|
+
typeof frame.messageRef !== "string" ||
|
|
120
|
+
frame.messageRef.length === 0
|
|
121
|
+
)
|
|
122
|
+
return undefined;
|
|
123
|
+
return `turn:${sessionId}:${frame.messageRef}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
95
126
|
/** One delivered frame reduced to a single event identity. */
|
|
96
127
|
type CorrelatedFrame = SessionRouterFrame;
|
|
97
128
|
|
|
@@ -384,8 +415,16 @@ export class ChatDaemonRuntime {
|
|
|
384
415
|
authorizeActor: async actorId => config.authorizedUserId === actorId,
|
|
385
416
|
resolveAttachment: async sessionId => this.#router.attachment(sessionId),
|
|
386
417
|
resolveBindingAuthority: async sessionId => await this.#router.bindingAuthority(sessionId),
|
|
387
|
-
onCommand: async (sessionId, content, attachment, idempotencyKey) =>
|
|
388
|
-
await this.#runChatCommand(
|
|
418
|
+
onCommand: async (sessionId, content, attachment, idempotencyKey, beforeDispatch, dispatchFence) =>
|
|
419
|
+
await this.#runChatCommand(
|
|
420
|
+
"slack",
|
|
421
|
+
sessionId,
|
|
422
|
+
content,
|
|
423
|
+
attachment,
|
|
424
|
+
idempotencyKey,
|
|
425
|
+
beforeDispatch,
|
|
426
|
+
dispatchFence,
|
|
427
|
+
),
|
|
389
428
|
});
|
|
390
429
|
}
|
|
391
430
|
try {
|
|
@@ -571,8 +610,9 @@ export class ChatDaemonRuntime {
|
|
|
571
610
|
if (this.#attachments.get(attachment.sessionId) !== attachment) return;
|
|
572
611
|
await this.#attachmentBarriers.get(attachment.sessionId);
|
|
573
612
|
if (this.#attachments.get(attachment.sessionId) !== attachment) return;
|
|
574
|
-
const publicationId = correlated.publicationId;
|
|
575
613
|
const normalizedFrame = correlated.body;
|
|
614
|
+
const publicationId =
|
|
615
|
+
publicationIdForFinalChatAnswer(attachment.sessionId, normalizedFrame) ?? correlated.publicationId;
|
|
576
616
|
const bodyType = typeof normalizedFrame.type === "string" ? normalizedFrame.type : undefined;
|
|
577
617
|
if (isControlPlaneFrameType(correlated.name) || isControlPlaneFrameType(bodyType)) return;
|
|
578
618
|
if (normalizedFrame.type === "turn_stream" && normalizedFrame.phase === "live") return;
|
|
@@ -607,6 +647,11 @@ export class ChatDaemonRuntime {
|
|
|
607
647
|
return;
|
|
608
648
|
}
|
|
609
649
|
if (!notification) return;
|
|
650
|
+
if (
|
|
651
|
+
notification.type === "frame" &&
|
|
652
|
+
!shouldPublishChatFrame(normalizedFrame, this.input.config.presentation?.verbosity)
|
|
653
|
+
)
|
|
654
|
+
return;
|
|
610
655
|
const payload = this.#presentation?.fanout(notification)[0];
|
|
611
656
|
const body = payload?.body;
|
|
612
657
|
const content =
|
|
@@ -694,6 +739,8 @@ export class ChatDaemonRuntime {
|
|
|
694
739
|
content: string,
|
|
695
740
|
expectedAttachment: SessionAttachment,
|
|
696
741
|
idempotencyKey: string = randomUUID(),
|
|
742
|
+
beforeDispatch?: () => void,
|
|
743
|
+
dispatchFence?: <T>(dispatch: () => Promise<T>) => Promise<T>,
|
|
697
744
|
): Promise<boolean> {
|
|
698
745
|
const match = /^\/sdk\s+(control|query|global)\s+([^\s]+)(?:\s+(.+))?\s*$/.exec(content);
|
|
699
746
|
if (!match) return false;
|
|
@@ -712,7 +759,13 @@ export class ChatDaemonRuntime {
|
|
|
712
759
|
try {
|
|
713
760
|
outcome = await sendAuthorizedChatOperation(transport, { kind, operation, input }, async () => {
|
|
714
761
|
if (kind === "global")
|
|
715
|
-
return await this.#runGlobalCommand(
|
|
762
|
+
return await this.#runGlobalCommand(
|
|
763
|
+
operation,
|
|
764
|
+
input as Record<string, unknown>,
|
|
765
|
+
idempotencyKey,
|
|
766
|
+
beforeDispatch,
|
|
767
|
+
dispatchFence,
|
|
768
|
+
);
|
|
716
769
|
if (!expectedAttachment.isCurrent()) throw new ChatDeliveryError("pre_send");
|
|
717
770
|
return await this.#router.request(
|
|
718
771
|
sessionId,
|
|
@@ -721,6 +774,12 @@ export class ChatDaemonRuntime {
|
|
|
721
774
|
: { type: "query_request", query: operation, input, idempotencyKey },
|
|
722
775
|
expectedAttachment.generation,
|
|
723
776
|
expectedAttachment,
|
|
777
|
+
beforeDispatch || dispatchFence
|
|
778
|
+
? {
|
|
779
|
+
beforeDispatch,
|
|
780
|
+
dispatchFence: dispatchFence ? dispatch => dispatchFence(dispatch) : undefined,
|
|
781
|
+
}
|
|
782
|
+
: undefined,
|
|
724
783
|
);
|
|
725
784
|
});
|
|
726
785
|
} catch (error) {
|
|
@@ -740,10 +799,17 @@ export class ChatDaemonRuntime {
|
|
|
740
799
|
operation: string,
|
|
741
800
|
input: Record<string, unknown>,
|
|
742
801
|
idempotencyKey: string,
|
|
802
|
+
beforeDispatch?: () => void,
|
|
803
|
+
dispatchFence?: <T>(dispatch: () => Promise<T>) => Promise<T>,
|
|
743
804
|
): Promise<Record<string, unknown>> {
|
|
744
805
|
if (operation !== "session.list") throw new ChatDeliveryError("pre_send");
|
|
745
806
|
try {
|
|
746
|
-
return await this.#router.listBrokerSessions(
|
|
807
|
+
return await this.#router.listBrokerSessions(
|
|
808
|
+
input,
|
|
809
|
+
idempotencyKey,
|
|
810
|
+
{ beforeDispatch },
|
|
811
|
+
dispatchFence ? dispatch => dispatchFence(dispatch) : undefined,
|
|
812
|
+
);
|
|
747
813
|
} catch (error) {
|
|
748
814
|
if (error instanceof SessionRouterError) throw new ChatDeliveryError(error.phase);
|
|
749
815
|
throw error;
|
|
@@ -14,7 +14,7 @@ export function chatEffectJournalPath(agentDir: string, transport: "discord" | "
|
|
|
14
14
|
return conversationStorePath(agentDir, transport, "effects.json");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export type ChatEffectState = "pending" | "leased" | "accepted" | "uncertain" | "terminal";
|
|
17
|
+
export type ChatEffectState = "pending" | "leased" | "dispatching" | "accepted" | "uncertain" | "terminal";
|
|
18
18
|
|
|
19
19
|
/** Provider receipts are identifiers/status only. Never put request or response bodies here. */
|
|
20
20
|
export interface ChatEffectReceipt {
|
|
@@ -262,7 +262,11 @@ export class ChatEffectJournal {
|
|
|
262
262
|
let renewed: ChatEffect<TPayload> | undefined;
|
|
263
263
|
const now = this.#now();
|
|
264
264
|
await this.#store.transact(id, current => {
|
|
265
|
-
if (
|
|
265
|
+
if (
|
|
266
|
+
(current?.state !== "leased" && current?.state !== "dispatching") ||
|
|
267
|
+
current.owner !== lease.owner ||
|
|
268
|
+
current.epoch !== lease.epoch
|
|
269
|
+
)
|
|
266
270
|
return current;
|
|
267
271
|
renewed = {
|
|
268
272
|
...current,
|
|
@@ -275,6 +279,78 @@ export class ChatEffectJournal {
|
|
|
275
279
|
return renewed;
|
|
276
280
|
}
|
|
277
281
|
|
|
282
|
+
async markDispatching<TPayload = unknown>(
|
|
283
|
+
id: string,
|
|
284
|
+
lease: ChatEffectLease,
|
|
285
|
+
): Promise<ChatEffect<TPayload> | undefined> {
|
|
286
|
+
let marked: ChatEffect<TPayload> | undefined;
|
|
287
|
+
const now = this.#now();
|
|
288
|
+
await this.#store.transact(id, current => {
|
|
289
|
+
if (current?.state !== "leased" || current.owner !== lease.owner || current.epoch !== lease.epoch)
|
|
290
|
+
return current;
|
|
291
|
+
marked = {
|
|
292
|
+
...current,
|
|
293
|
+
generation: current.generation + 1,
|
|
294
|
+
state: "dispatching",
|
|
295
|
+
updatedAt: now,
|
|
296
|
+
} as ChatEffect<TPayload>;
|
|
297
|
+
return marked;
|
|
298
|
+
});
|
|
299
|
+
return marked;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async ownsDispatching(id: string, lease: ChatEffectLease): Promise<boolean> {
|
|
303
|
+
const current = await this.#store.read(id);
|
|
304
|
+
return (
|
|
305
|
+
current?.state === "dispatching" &&
|
|
306
|
+
current.owner === lease.owner &&
|
|
307
|
+
current.epoch === lease.epoch &&
|
|
308
|
+
(current.leaseExpiresAt ?? 0) > this.#now()
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Hold the durable dispatching lease until the synchronous SDK prewire boundary. */
|
|
313
|
+
async dispatchWithLeaseFence<R>(
|
|
314
|
+
id: string,
|
|
315
|
+
lease: ChatEffectLease,
|
|
316
|
+
dispatch: () => Promise<R>,
|
|
317
|
+
dispatched: Promise<void>,
|
|
318
|
+
): Promise<{ value: R } | undefined> {
|
|
319
|
+
return await this.#store.dispatchWithSnapshotFence(id, current => {
|
|
320
|
+
if (
|
|
321
|
+
current?.state !== "dispatching" ||
|
|
322
|
+
current.owner !== lease.owner ||
|
|
323
|
+
current.epoch !== lease.epoch ||
|
|
324
|
+
(current.leaseExpiresAt ?? 0) <= this.#now()
|
|
325
|
+
)
|
|
326
|
+
return undefined;
|
|
327
|
+
return { result: dispatch(), dispatched };
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async recoverDispatchingAsUncertain<TPayload = unknown>(
|
|
332
|
+
id: string,
|
|
333
|
+
options: { onlyIfExpired?: boolean } = {},
|
|
334
|
+
): Promise<ChatEffect<TPayload> | undefined> {
|
|
335
|
+
let recovered: ChatEffect<TPayload> | undefined;
|
|
336
|
+
const now = this.#now();
|
|
337
|
+
await this.#store.transact(id, current => {
|
|
338
|
+
if (current?.state !== "dispatching") return current;
|
|
339
|
+
if (options.onlyIfExpired && (current.leaseExpiresAt ?? 0) > now) return current;
|
|
340
|
+
recovered = {
|
|
341
|
+
...current,
|
|
342
|
+
generation: current.generation + 1,
|
|
343
|
+
state: "uncertain",
|
|
344
|
+
owner: undefined,
|
|
345
|
+
leaseExpiresAt: undefined,
|
|
346
|
+
receipt: { status: "uncertain" },
|
|
347
|
+
updatedAt: now,
|
|
348
|
+
} as ChatEffect<TPayload>;
|
|
349
|
+
return recovered;
|
|
350
|
+
});
|
|
351
|
+
return recovered;
|
|
352
|
+
}
|
|
353
|
+
|
|
278
354
|
/** Persists provider progress without releasing the owner/epoch fence. */
|
|
279
355
|
async recordReceipt<TPayload = unknown>(
|
|
280
356
|
id: string,
|
|
@@ -284,7 +360,11 @@ export class ChatEffectJournal {
|
|
|
284
360
|
let recorded: ChatEffect<TPayload> | undefined;
|
|
285
361
|
const now = this.#now();
|
|
286
362
|
await this.#store.transact(id, current => {
|
|
287
|
-
if (
|
|
363
|
+
if (
|
|
364
|
+
(current?.state !== "leased" && current?.state !== "dispatching") ||
|
|
365
|
+
current.owner !== lease.owner ||
|
|
366
|
+
current.epoch !== lease.epoch
|
|
367
|
+
)
|
|
288
368
|
return current;
|
|
289
369
|
recorded = {
|
|
290
370
|
...current,
|
|
@@ -300,13 +380,17 @@ export class ChatEffectJournal {
|
|
|
300
380
|
async record<TPayload = unknown>(
|
|
301
381
|
id: string,
|
|
302
382
|
lease: ChatEffectLease,
|
|
303
|
-
state:
|
|
304
|
-
receipt
|
|
383
|
+
state: "accepted" | "uncertain" | "terminal",
|
|
384
|
+
receipt: ChatEffectReceipt = {},
|
|
305
385
|
): Promise<ChatEffect<TPayload> | undefined> {
|
|
306
386
|
let recorded: ChatEffect<TPayload> | undefined;
|
|
307
387
|
const now = this.#now();
|
|
308
388
|
await this.#store.transact(id, current => {
|
|
309
|
-
if (
|
|
389
|
+
if (
|
|
390
|
+
(current?.state !== "leased" && current?.state !== "dispatching") ||
|
|
391
|
+
current.owner !== lease.owner ||
|
|
392
|
+
current.epoch !== lease.epoch
|
|
393
|
+
)
|
|
310
394
|
return current;
|
|
311
395
|
recorded = {
|
|
312
396
|
...current,
|
|
@@ -329,7 +413,10 @@ export class ChatEffectJournal {
|
|
|
329
413
|
const now = this.#now();
|
|
330
414
|
await this.#store.transact(id, current => {
|
|
331
415
|
if (!current || current.state === "terminal") return current;
|
|
332
|
-
if (
|
|
416
|
+
if (
|
|
417
|
+
(current.state === "leased" || current.state === "dispatching") &&
|
|
418
|
+
(!lease || current.owner !== lease.owner || current.epoch !== lease.epoch)
|
|
419
|
+
)
|
|
333
420
|
return current;
|
|
334
421
|
|
|
335
422
|
terminalized = {
|
|
@@ -347,6 +434,44 @@ export class ChatEffectJournal {
|
|
|
347
434
|
return terminalized;
|
|
348
435
|
}
|
|
349
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Terminalize only a decided effect. The uncertainty check and state change
|
|
439
|
+
* share one store transaction so a concurrent sender cannot publish an
|
|
440
|
+
* ambiguity tombstone between a caller's read and stale cleanup.
|
|
441
|
+
*/
|
|
442
|
+
async terminalizeDecided(
|
|
443
|
+
id: string,
|
|
444
|
+
receipt: ChatEffectReceipt,
|
|
445
|
+
lease?: ChatEffectLease,
|
|
446
|
+
): Promise<ChatEffect | undefined> {
|
|
447
|
+
let terminalized: ChatEffect | undefined;
|
|
448
|
+
const now = this.#now();
|
|
449
|
+
await this.#store.transact(id, current => {
|
|
450
|
+
if (!current || current.state === "uncertain") return current;
|
|
451
|
+
if (current.state === "terminal") {
|
|
452
|
+
terminalized = current;
|
|
453
|
+
return current;
|
|
454
|
+
}
|
|
455
|
+
if (
|
|
456
|
+
(current.state === "leased" || current.state === "dispatching") &&
|
|
457
|
+
(!lease || current.owner !== lease.owner || current.epoch !== lease.epoch)
|
|
458
|
+
)
|
|
459
|
+
return current;
|
|
460
|
+
terminalized = {
|
|
461
|
+
...current,
|
|
462
|
+
generation: current.generation + 1,
|
|
463
|
+
state: "terminal",
|
|
464
|
+
owner: undefined,
|
|
465
|
+
leaseExpiresAt: undefined,
|
|
466
|
+
receipt,
|
|
467
|
+
updatedAt: now,
|
|
468
|
+
};
|
|
469
|
+
return terminalized;
|
|
470
|
+
});
|
|
471
|
+
if (terminalized?.state === "terminal") await this.#pruneTerminal();
|
|
472
|
+
return terminalized;
|
|
473
|
+
}
|
|
474
|
+
|
|
350
475
|
async #pruneTerminal(): Promise<void> {
|
|
351
476
|
const terminal = (await this.list())
|
|
352
477
|
.filter(effect => effect.state === "terminal")
|
|
@@ -208,6 +208,35 @@ export class ConversationStore<T extends ConversationRecord> {
|
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Hold the cross-process mapping lock until a prepared dispatch either reaches
|
|
213
|
+
* its synchronous wire boundary or fails before sending. The result may remain
|
|
214
|
+
* pending after the lock is released while the remote response is awaited.
|
|
215
|
+
*/
|
|
216
|
+
async dispatchWithSnapshotFence<R>(
|
|
217
|
+
key: string,
|
|
218
|
+
prepare: (
|
|
219
|
+
current: T | undefined,
|
|
220
|
+
conversations: Readonly<Record<string, T>>,
|
|
221
|
+
) => { result: Promise<R>; dispatched: Promise<void> } | undefined,
|
|
222
|
+
): Promise<{ value: R } | undefined> {
|
|
223
|
+
let result: Promise<R> | undefined;
|
|
224
|
+
await this.#withLock(async () => {
|
|
225
|
+
const document = await this.#readDocument();
|
|
226
|
+
const prepared = prepare(document.conversations[key], Object.freeze({ ...document.conversations }));
|
|
227
|
+
if (!prepared) return;
|
|
228
|
+
result = prepared.result;
|
|
229
|
+
await Promise.race([
|
|
230
|
+
prepared.dispatched,
|
|
231
|
+
prepared.result.then(
|
|
232
|
+
() => undefined,
|
|
233
|
+
() => undefined,
|
|
234
|
+
),
|
|
235
|
+
]);
|
|
236
|
+
});
|
|
237
|
+
return result ? { value: await result } : undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
211
240
|
async #withLock<R>(operation: () => Promise<R>): Promise<R> {
|
|
212
241
|
const previous = this.#locks.get(this.#file) ?? Promise.resolve();
|
|
213
242
|
const gate = Promise.withResolvers<void>();
|
|
@@ -15,10 +15,15 @@ import * as os from "node:os";
|
|
|
15
15
|
import type { SessionCloseTarget, SessionCreateTarget, SessionLifecycleResponse, SessionResumeTarget } from "./index";
|
|
16
16
|
|
|
17
17
|
export type LifecycleCommandVerb = "session_create" | "session_close" | "session_resume";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
|
|
19
|
+
export interface LifecycleCommandContext {
|
|
20
|
+
chatType?: string;
|
|
21
|
+
botUsername?: string;
|
|
22
|
+
/** Session durably associated with the Telegram topic containing the command. */
|
|
23
|
+
threadSessionId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeLifecycleCommandToken(token: string, ctx: LifecycleCommandContext = {}): string | undefined {
|
|
22
27
|
const at = token.indexOf("@");
|
|
23
28
|
const command = at === -1 ? token : token.slice(0, at);
|
|
24
29
|
if (!/^\/session_(create|close|resume|recent)\b/.test(command)) return undefined;
|
|
@@ -44,8 +49,8 @@ const USAGE = [
|
|
|
44
49
|
"/session_create path <dir> [--mpreset <profile>]",
|
|
45
50
|
"/session_create worktree <repo> <branch> [--mpreset <profile>]",
|
|
46
51
|
"/session_create dir <newdir> [--mpreset <profile>]",
|
|
47
|
-
"/session_close <sessionId>",
|
|
48
|
-
"/session_resume <sessionId|prefix>",
|
|
52
|
+
"/session_close <sessionId> (ID optional inside its session thread)",
|
|
53
|
+
"/session_resume <sessionId|prefix> (ID optional inside its session thread)",
|
|
49
54
|
"/session_recent [create|resume]",
|
|
50
55
|
].join("\n");
|
|
51
56
|
|
|
@@ -103,10 +108,7 @@ export function isLifecycleCommandLikeText(text: string | undefined): boolean {
|
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
/** True when the text begins an addressable /session_* command (cheap pre-gate). */
|
|
106
|
-
export function isLifecycleCommandText(
|
|
107
|
-
text: string | undefined,
|
|
108
|
-
ctx: { chatType?: string; botUsername?: string } = {},
|
|
109
|
-
): boolean {
|
|
111
|
+
export function isLifecycleCommandText(text: string | undefined, ctx: LifecycleCommandContext = {}): boolean {
|
|
110
112
|
if (!text) return false;
|
|
111
113
|
const [rawCommand] = text.trim().split(/\s+/, 1);
|
|
112
114
|
return normalizeLifecycleCommandToken(rawCommand ?? "", ctx) !== undefined;
|
|
@@ -139,7 +141,7 @@ function extractModelPreset(args: string[]): { positional: string[]; modelPreset
|
|
|
139
141
|
*/
|
|
140
142
|
export function parseLifecycleCommand(
|
|
141
143
|
text: string | undefined,
|
|
142
|
-
ctx:
|
|
144
|
+
ctx: LifecycleCommandContext = {},
|
|
143
145
|
): ParsedLifecycleCommand {
|
|
144
146
|
const tokens = tokenizeLifecycleCommand((text ?? "").trim());
|
|
145
147
|
if (!tokens) return { kind: "usage", message: USAGE };
|
|
@@ -166,6 +168,8 @@ export function parseLifecycleCommand(
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
if (command === "/session_close") {
|
|
171
|
+
if (args.length === 0 && ctx.threadSessionId)
|
|
172
|
+
return { kind: "close", target: { sessionId: ctx.threadSessionId } };
|
|
169
173
|
if (args.length !== 1) return { kind: "usage", message: USAGE };
|
|
170
174
|
const sessionId = args[0]!;
|
|
171
175
|
if (!isSafeIdentifier(sessionId)) {
|
|
@@ -175,6 +179,8 @@ export function parseLifecycleCommand(
|
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
if (command === "/session_resume") {
|
|
182
|
+
if (args.length === 0 && ctx.threadSessionId)
|
|
183
|
+
return { kind: "resume", target: { sessionIdOrPrefix: ctx.threadSessionId } };
|
|
178
184
|
if (args.length !== 1) return { kind: "usage", message: USAGE };
|
|
179
185
|
const idOrPrefix = args[0]!;
|
|
180
186
|
if (!isSafeIdentifier(idOrPrefix)) {
|
|
@@ -76,6 +76,7 @@ export function normalizeSlackConversation(record: SlackConversation): SlackConv
|
|
|
76
76
|
export function acceptsSlackInbound(record: SlackConversation, rootTs: string, endpointGeneration: number): boolean {
|
|
77
77
|
return (
|
|
78
78
|
(record.state === "active" || record.state === "resumed_root") &&
|
|
79
|
+
record.cleanupEffectId === undefined &&
|
|
79
80
|
record.rootTs === rootTs &&
|
|
80
81
|
record.endpointGeneration === endpointGeneration
|
|
81
82
|
);
|