@cortexkit/aft-opencode 0.58.1 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bash-wait-detach.d.ts.map +1 -1
- package/dist/bg-notifications.d.ts +8 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/bridge-bootstrap.d.ts +4 -4
- package/dist/config-live-reload.d.ts +44 -8
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +85 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts.map +1 -1
- package/dist/entry/server.js +5329 -2607
- package/dist/hashline-tag-sources.d.ts +18 -0
- package/dist/hashline-tag-sources.d.ts.map +1 -0
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4511 -2373
- package/dist/lsp-auto-install.d.ts +30 -0
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp-github-install.d.ts +5 -2
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/lsp-github-table.d.ts +4 -3
- package/dist/lsp-github-table.d.ts.map +1 -1
- package/dist/lsp-npm-table.d.ts +10 -0
- package/dist/lsp-npm-table.d.ts.map +1 -1
- package/dist/lsp-registry-probe.d.ts +8 -4
- package/dist/lsp-registry-probe.d.ts.map +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/permissions/v2-service.d.ts +110 -5
- package/dist/permissions/v2-service.d.ts.map +1 -1
- package/dist/rpc/contract.d.ts +1 -1
- package/dist/rpc/contract.d.ts.map +1 -1
- package/dist/rpc/register.d.ts +3 -5
- package/dist/rpc/register.d.ts.map +1 -1
- package/dist/shared/ignored-message.d.ts.map +1 -1
- package/dist/shared/read-vision.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +8 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/session-directory.d.ts +9 -0
- package/dist/shared/session-directory.d.ts.map +1 -1
- package/dist/shared/status.d.ts +1 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/shared/subagent-detect.d.ts +11 -4
- package/dist/shared/subagent-detect.d.ts.map +1 -1
- package/dist/shared/v2-context.d.ts +3 -0
- package/dist/shared/v2-context.d.ts.map +1 -0
- package/dist/subc-tool-schemas.d.ts +23 -1
- package/dist/subc-tool-schemas.d.ts.map +1 -1
- package/dist/tool-registration.d.ts +11 -8
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +16 -15
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +41 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +14 -4
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/bash_write.d.ts.map +1 -1
- package/dist/tools/definitions/v2.d.ts +4 -0
- package/dist/tools/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted/v2.d.ts +8 -1
- package/dist/tools/hoisted/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +27 -7
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts +10 -1
- package/dist/tools/inspect.d.ts.map +1 -1
- package/dist/tools/permissions.d.ts +2 -1
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/reading.d.ts +0 -3
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui/aft-rpc.d.ts +14 -0
- package/dist/tui/aft-rpc.d.ts.map +1 -1
- package/dist/tui/v2-status.d.ts.map +1 -1
- package/dist/v2-host-tool-overlap.d.ts +66 -0
- package/dist/v2-host-tool-overlap.d.ts.map +1 -0
- package/dist/v2-prompt-detach.d.ts +70 -0
- package/dist/v2-prompt-detach.d.ts.map +1 -0
- package/dist/v2-tool-hooks.d.ts +6 -0
- package/dist/v2-tool-hooks.d.ts.map +1 -0
- package/dist/v2-workflow-hints.d.ts +4 -0
- package/dist/v2-workflow-hints.d.ts.map +1 -0
- package/dist/wakes/runtime-consumer.d.ts +2 -0
- package/dist/wakes/runtime-consumer.d.ts.map +1 -1
- package/dist/wakes/session-delivery.d.ts +9 -0
- package/dist/wakes/session-delivery.d.ts.map +1 -1
- package/dist/workflow-hints.d.ts +20 -7
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/entry/server-runtime.mjs +41 -5
- package/src/shared/ignored-message.ts +18 -0
- package/src/shared/read-vision.ts +14 -4
- package/src/shared/rpc-client.ts +46 -5
- package/src/shared/rpc-server.ts +10 -0
- package/src/shared/session-directory.ts +147 -7
- package/src/shared/status.ts +15 -5
- package/src/shared/subagent-detect.ts +89 -28
- package/src/shared/v2-context.ts +7 -0
- package/src/tui/aft-rpc.ts +11 -0
- package/src/tui/index.tsx +20 -16
- package/src/tui/sidebar-view.tsx +9 -1
- package/src/tui/v2-status.ts +7 -5
- package/src/tui/v2.tsx +47 -0
- package/src/tui-compiled/aft-rpc.ts +11 -0
- package/src/tui-compiled/index.tsx +27 -16
- package/src/tui-compiled/sidebar-view.tsx +11 -2
- package/src/tui-compiled/v2-status.ts +7 -5
- package/src/tui-compiled/v2.tsx +26 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { interruptBashWaitsForChatMessage } from "./bash-wait-detach.js";
|
|
3
|
+
import type { AftConfig } from "./config.js";
|
|
4
|
+
/** An offset range into `prompt.text`; `end` is exclusive. */
|
|
5
|
+
interface PromptMention {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
interface MentionCarrier {
|
|
11
|
+
mention?: PromptMention;
|
|
12
|
+
}
|
|
13
|
+
/** The fields of OpenCode 2's `SessionHooks["prompt"]` event this hook reads or edits. */
|
|
14
|
+
export interface V2PromptHookEvent {
|
|
15
|
+
readonly sessionID: string;
|
|
16
|
+
prompt: {
|
|
17
|
+
text: string;
|
|
18
|
+
files?: MentionCarrier[];
|
|
19
|
+
agents?: MentionCarrier[];
|
|
20
|
+
skills?: MentionCarrier[];
|
|
21
|
+
};
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
type ChatMessagePool = Parameters<typeof interruptBashWaitsForChatMessage>[0];
|
|
25
|
+
export interface V2PromptDetachRuntime {
|
|
26
|
+
/** The Location's bridge pool, where a session's waits live. */
|
|
27
|
+
readonly pool: ChatMessagePool;
|
|
28
|
+
/**
|
|
29
|
+
* The root this Location's tools run on (the Location's own directory); its
|
|
30
|
+
* bridge is asked to detach before any other.
|
|
31
|
+
*/
|
|
32
|
+
readonly projectRoot: string;
|
|
33
|
+
/**
|
|
34
|
+
* Read on every prompt so a live config reload of
|
|
35
|
+
* `bash.detach_on_user_message` applies to the next message.
|
|
36
|
+
*/
|
|
37
|
+
getConfig(): AftConfig;
|
|
38
|
+
registerSession?: (sessionID: string) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Run the shared chat-message decision on one OpenCode 2 prompt and write the
|
|
42
|
+
* stripped text (and moved mention offsets) back onto it. Returns whether the
|
|
43
|
+
* session's waits were interrupted.
|
|
44
|
+
*
|
|
45
|
+
* A prompt AFT admitted itself (a completion wake) is passed to the shared
|
|
46
|
+
* decision as a synthetic part, exactly as OpenCode 1 marks such parts: it
|
|
47
|
+
* interrupts a wait whenever `bash.detach_on_user_message` is on, but its text
|
|
48
|
+
* is never read for `&detach` and never edited.
|
|
49
|
+
*
|
|
50
|
+
* All edits are computed before any is applied, so a throw leaves the prompt
|
|
51
|
+
* exactly as it arrived.
|
|
52
|
+
*/
|
|
53
|
+
export declare function interruptBashWaitsForV2Prompt(runtime: V2PromptDetachRuntime, event: V2PromptHookEvent): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The callback registered as OpenCode 2's session `prompt` hook. The host
|
|
56
|
+
* waits for it before admitting the message and a failure would reject the
|
|
57
|
+
* message, so it never fails and never waits: errors are caught, logged once
|
|
58
|
+
* per distinct reason, and the prompt passes through unchanged; the bridge
|
|
59
|
+
* detach request it may start is not awaited.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createV2PromptDetachHook(runtime: V2PromptDetachRuntime): (event: V2PromptHookEvent) => Effect.Effect<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Register the prompt hook on an OpenCode 2 host context. The registration is
|
|
64
|
+
* scoped by the host, so it ends with the Location. A host context without
|
|
65
|
+
* `session.hook` gets no hook, which leaves waits un-detachable by messages
|
|
66
|
+
* but otherwise working.
|
|
67
|
+
*/
|
|
68
|
+
export declare function registerV2PromptDetachHook(context: unknown, runtime: V2PromptDetachRuntime): Effect.Effect<boolean, never, unknown>;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=v2-prompt-detach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-prompt-detach.d.ts","sourceRoot":"","sources":["../src/v2-prompt-detach.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,8DAA8D;AAC9D,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,0FAA0F;AAC1F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;QACzB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;QAC1B,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9E,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,SAAS,IAAI,SAAS,CAAC;IACvB,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAqCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAuBT;AAMD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,qBAAqB,GAC7B,CAAC,KAAK,EAAE,iBAAiB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAgBnD;AAOD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,qBAAqB,GAC7B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAYxC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
/** Port the V1 raw-argument, result-text and current-model seams to V2. */
|
|
3
|
+
export declare function registerV2ToolHooks(context: unknown, runtime: {
|
|
4
|
+
hashlineEffective?: boolean;
|
|
5
|
+
}, registeredTools: ReadonlySet<string>): Effect.Effect<void, never, import("effect/Scope").Scope>;
|
|
6
|
+
//# sourceMappingURL=v2-tool-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-tool-hooks.d.ts","sourceRoot":"","sources":["../src/v2-tool-hooks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAqBxC,2EAA2E;AAC3E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,EACxC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,4DA6DrC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
/** V2 consumes session system hooks, not V1's chat.system.transform. */
|
|
3
|
+
export declare function registerV2WorkflowHints(context: unknown, hintsBlock: string | null): Effect.Effect<boolean, never, import("effect/Scope").Scope>;
|
|
4
|
+
//# sourceMappingURL=v2-workflow-hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-workflow-hints.d.ts","sourceRoot":"","sources":["../src/v2-workflow-hints.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,+DAelF"}
|
|
@@ -4,6 +4,8 @@ import { type V2SessionAdmission } from "./session-delivery.js";
|
|
|
4
4
|
export interface V2WakeHostContext {
|
|
5
5
|
readonly session: V2SessionAdmission;
|
|
6
6
|
}
|
|
7
|
+
/** Test-only: forget every route and held completion in the process-wide table. */
|
|
8
|
+
export declare function __resetV2WakeRoutesForTests(): void;
|
|
7
9
|
declare const sharedBridgeOptions: Pick<BridgeOptions, "onBashCompletion" | "onBashLongRunning">;
|
|
8
10
|
export interface V2RuntimeConsumer extends V2ToolConsumers {
|
|
9
11
|
readonly bridgeOptions: typeof sharedBridgeOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-consumer.d.ts","sourceRoot":"","sources":["../../src/wakes/runtime-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-consumer.d.ts","sourceRoot":"","sources":["../../src/wakes/runtime-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,uBAAuB,CAAC;AAEnF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC;AAyJD,mFAAmF;AACnF,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED,QAAA,MAAM,mBAAmB,EAAE,IAAI,CAAC,aAAa,EAAE,kBAAkB,GAAG,mBAAmB,CAGtF,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,QAAQ,CAAC,aAAa,EAAE,OAAO,mBAAmB,CAAC;IACnD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAiErF"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* The `metadata.source` value on every prompt AFT admits into a session itself
|
|
4
|
+
* (background-completion wakes). OpenCode 2 runs every prompt, AFT's own
|
|
5
|
+
* included, through the session `prompt` hook; the hook reads this marker to
|
|
6
|
+
* tell AFT's machine-written text from text an operator typed, never the text.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AFT_PROMPT_SOURCE = "aft";
|
|
9
|
+
/** True when a prompt's metadata marks it as one AFT admitted itself. */
|
|
10
|
+
export declare function isAftOriginatedPrompt(metadata: unknown): boolean;
|
|
2
11
|
export interface V2SessionPromptInput {
|
|
3
12
|
readonly sessionID: string;
|
|
4
13
|
readonly text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-delivery.d.ts","sourceRoot":"","sources":["../../src/wakes/session-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"session-delivery.d.ts","sourceRoot":"","sources":["../../src/wakes/session-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAMhE;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrE,SAAS,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED;;;GAGG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAS;gBAEI,OAAO,EAAE,kBAAkB;IAExD,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;IAqClE,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC;IAe7D,OAAO,IAAI,IAAI;CAKhB"}
|
package/dist/workflow-hints.d.ts
CHANGED
|
@@ -4,20 +4,25 @@ export interface WorkflowHintsOpts {
|
|
|
4
4
|
bashBackgroundEnabled: boolean;
|
|
5
5
|
/** Resolved bash compression flag. */
|
|
6
6
|
bashCompressionEnabled: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Resolved `bash.rewrite` flag. Only rewritten `cat`/`head`/`tail` commands
|
|
9
|
+
* mint hashline tags through bash, so the hashline hint names them only when
|
|
10
|
+
* rewriting is on. Omitted means on.
|
|
11
|
+
*/
|
|
12
|
+
bashRewriteEnabled?: boolean;
|
|
7
13
|
/** Set of tool names that are not registered. */
|
|
8
14
|
disabledTools: Set<string>;
|
|
9
15
|
/** Whether the hashline `edit` arm is the one actually registered. */
|
|
10
16
|
hashlineEffective?: boolean;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
|
-
* Routing rule for hashline sessions
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* snapshot, so an agent that inspects a symbol and then patches it is refused
|
|
17
|
-
* for a tag it believes it already has. Naming the tag-minting tools is the
|
|
18
|
-
* only way to distinguish "I have seen this code" from "I can address it".
|
|
19
|
+
* Routing rule for hashline sessions: which calls mint the tags a patch
|
|
20
|
+
* addresses (see `hashlineTagSourceSentence`). The bash rewrite path and each
|
|
21
|
+
* AFT navigation tool are named only when present.
|
|
19
22
|
*/
|
|
20
|
-
export declare
|
|
23
|
+
export declare function hashlineTagSourceHint(bashRewritesMintTags: boolean, isRegistered?: (toolName: string) => boolean): string;
|
|
24
|
+
/** The hashline hint for a session whose bash rewrites `cat`/`head`/`tail`. */
|
|
25
|
+
export declare const HASHLINE_TAG_SOURCE_HINT: string;
|
|
21
26
|
/**
|
|
22
27
|
* Build the workflow hints block. Returns `null` when no hints are
|
|
23
28
|
* applicable for the configured surface (e.g. `tool_surface: "minimal"`
|
|
@@ -32,6 +37,14 @@ export declare function buildWorkflowHints(opts: WorkflowHintsOpts): string | nu
|
|
|
32
37
|
* `bash.background` setting controls whether the hint appears.
|
|
33
38
|
*/
|
|
34
39
|
export declare function buildHintsFromConfig(config: AftConfig, disabledTools: Set<string>, hashlineEffective?: boolean): string | null;
|
|
40
|
+
/** Every tool name a workflow-hints section can mention. */
|
|
41
|
+
export declare const WORKFLOW_HINT_TOOL_NAMES: readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Build the hints block for the tools that are actually registered. Every
|
|
44
|
+
* hint-relevant name missing from `registeredTools` is passed as absent, so
|
|
45
|
+
* sections for tools the agent cannot call are dropped.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildHintsForRegisteredTools(config: AftConfig, registeredTools: ReadonlySet<string>, hashlineEffective?: boolean): string | null;
|
|
35
48
|
/**
|
|
36
49
|
* Fold the hints block into a host `system[]` array in place.
|
|
37
50
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,aAAa,CAAC;AAGhE,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,qBAAqB,EAAE,OAAO,CAAC;IAC/B,sCAAsC;IACtC,sBAAsB,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iDAAiD;IACjD,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAID;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,oBAAoB,EAAE,OAAO,EAC7B,YAAY,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAoB,GACvD,MAAM,CAER;AAED,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,QAA8B,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAkJzE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,EAC1B,iBAAiB,UAAQ,GACxB,MAAM,GAAG,IAAI,CASf;AAED,4DAA4D;AAC5D,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAYrD,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,SAAS,EACjB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,iBAAiB,UAAQ,GACxB,MAAM,GAAG,IAAI,CAGf;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAQ9E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"schema": "bun scripts/build-schema.ts",
|
|
34
34
|
"build:tool-schemas": "bun run scripts/build-tool-schemas.ts",
|
|
35
35
|
"prepublishOnly": "bun run build",
|
|
36
|
-
"test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'",
|
|
36
|
+
"test:unit": "bun test src/__tests__ ./test/ ./__tests__/ --path-ignore-patterns 'src/__tests__/e2e/**'",
|
|
37
37
|
"build:tui": "bun scripts/build-tui.ts",
|
|
38
38
|
"smoke:tui": "bun test ./scripts/smoke-tui-pack-install.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@clack/prompts": "^1.6.0",
|
|
42
|
-
"@cortexkit/aft-bridge": "0.
|
|
42
|
+
"@cortexkit/aft-bridge": "0.59.0",
|
|
43
43
|
"@opencode/client": "2.0.11",
|
|
44
44
|
"@opentui/core": "0.4.3",
|
|
45
45
|
"@opentui/solid": "0.4.3",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"zod": "^4.4.3"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
54
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
55
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
56
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
57
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
58
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
53
|
+
"@cortexkit/aft-darwin-arm64": "0.59.0",
|
|
54
|
+
"@cortexkit/aft-darwin-x64": "0.59.0",
|
|
55
|
+
"@cortexkit/aft-linux-arm64": "0.59.0",
|
|
56
|
+
"@cortexkit/aft-linux-x64": "0.59.0",
|
|
57
|
+
"@cortexkit/aft-win32-arm64": "0.59.0",
|
|
58
|
+
"@cortexkit/aft-win32-x64": "0.59.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@opencode-ai/plugin": "^1.17.11",
|
|
@@ -17,8 +17,13 @@ import { startOpenCodeLiveConfigReload } from "../config-live-reload.js";
|
|
|
17
17
|
import { debug, log, warn } from "../logger.js";
|
|
18
18
|
import { resolvePluginVersion } from "../plugin-version.js";
|
|
19
19
|
import { registerAftConfigErrorRpc, registerAftRpc } from "../rpc/register.js";
|
|
20
|
-
import { hoistedV2ToolConsumers } from "../tools/hoisted/v2.js";
|
|
20
|
+
import { hoistedV2ToolConsumers, v2PromptChannelFor } from "../tools/hoisted/v2.js";
|
|
21
|
+
import { registerV2HostToolOverlapNotice } from "../v2-host-tool-overlap.js";
|
|
22
|
+
import { registerV2PromptDetachHook } from "../v2-prompt-detach.js";
|
|
23
|
+
import { registerV2ToolHooks } from "../v2-tool-hooks.js";
|
|
24
|
+
import { registerV2WorkflowHints } from "../v2-workflow-hints.js";
|
|
21
25
|
import { createV2RuntimeConsumer } from "../wakes/runtime-consumer.js";
|
|
26
|
+
import { buildHintsForRegisteredTools } from "../workflow-hints.js";
|
|
22
27
|
import {
|
|
23
28
|
buildAftToolDefinitions,
|
|
24
29
|
openCodeHashlineEffective,
|
|
@@ -34,8 +39,14 @@ const defaults = {
|
|
|
34
39
|
registerTools: registerAftTools,
|
|
35
40
|
registerRpc: registerAftRpc,
|
|
36
41
|
registerConfigErrorRpc: registerAftConfigErrorRpc,
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
registerPromptHook: registerV2PromptDetachHook,
|
|
43
|
+
registerToolHooks: registerV2ToolHooks,
|
|
44
|
+
registerWorkflowHints: registerV2WorkflowHints,
|
|
45
|
+
registerHostToolOverlapNotice: registerV2HostToolOverlapNotice,
|
|
46
|
+
// The prompt server comes from the user-only `opencode` block of the
|
|
47
|
+
// Location's config; without it AFT finds the server it runs inside.
|
|
48
|
+
toolConsumers: (context, config) => ({
|
|
49
|
+
...hoistedV2ToolConsumers(context, v2PromptChannelFor(config?.opencode)),
|
|
39
50
|
...createV2RuntimeConsumer(context),
|
|
40
51
|
}),
|
|
41
52
|
acquireBridge,
|
|
@@ -75,7 +86,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
75
86
|
dependencies,
|
|
76
87
|
);
|
|
77
88
|
const canonicalDirectory = location.project?.canonical ?? directory;
|
|
78
|
-
const consumers = dependencies.toolConsumers(context);
|
|
89
|
+
const consumers = dependencies.toolConsumers(context, config);
|
|
79
90
|
// Only a rejected configuration keeps AFT out of a project; there is no
|
|
80
91
|
// config switch that turns it off.
|
|
81
92
|
const isProjectEnabled = createProjectAcceptance(directory, dependencies);
|
|
@@ -115,6 +126,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
115
126
|
const { hashlineEditRegistered } = applyToolSurfaceOverrides(pool, config, registeredTools);
|
|
116
127
|
reportHashlineDowngrade(config, registeredTools, notify);
|
|
117
128
|
toolContext.hashlineEffective = hashlineEditRegistered;
|
|
129
|
+
const hintsBlock = buildHintsForRegisteredTools(config, registeredTools, hashlineEditRegistered);
|
|
118
130
|
// Keep this Location's live config keys current when a config file changes.
|
|
119
131
|
// The finalizer that releases the bridge also stops the watch.
|
|
120
132
|
const liveConfigReload = dependencies.startLiveConfigReload({
|
|
@@ -127,7 +139,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
127
139
|
},
|
|
128
140
|
notify,
|
|
129
141
|
});
|
|
130
|
-
return { consumers, pool, tools, liveConfigReload };
|
|
142
|
+
return { consumers, pool, tools, liveConfigReload, toolContext, directory, hintsBlock };
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
/**
|
|
@@ -206,12 +218,36 @@ export function makeServerEffect(overrides = {}) {
|
|
|
206
218
|
);
|
|
207
219
|
const rpc = yield* dependencies.registerRpc(context, location, runtime.pool);
|
|
208
220
|
yield* Effect.addFinalizer(() => Effect.promise(() => rpc.dispose()));
|
|
221
|
+
// OpenCode 2 has no chat.message hook; its session prompt hook is where a
|
|
222
|
+
// new message detaches a waiting bash, as chat.message does on OpenCode 1.
|
|
223
|
+
// The config is read through the tool context so live reloads apply.
|
|
224
|
+
// The bridge asked first is the one this Location's tools run on, the
|
|
225
|
+
// Location's own directory; for a Location in a linked git worktree that
|
|
226
|
+
// is the worktree, not the canonical main checkout the pool was acquired
|
|
227
|
+
// for (issue #387). Other bridges are still tried after it.
|
|
228
|
+
yield* dependencies.registerPromptHook(context, {
|
|
229
|
+
pool: runtime.pool,
|
|
230
|
+
projectRoot: runtime.directory,
|
|
231
|
+
getConfig: () => runtime.toolContext.config,
|
|
232
|
+
registerSession: runtime.consumers.registerSession,
|
|
233
|
+
});
|
|
209
234
|
yield* dependencies.registerTools(
|
|
210
235
|
context,
|
|
211
236
|
location,
|
|
212
237
|
runtime.tools,
|
|
213
238
|
runtime.consumers,
|
|
214
239
|
);
|
|
240
|
+
yield* dependencies.registerToolHooks(context, runtime.toolContext, new Set(Object.keys(runtime.tools)));
|
|
241
|
+
yield* dependencies.registerWorkflowHints(context, runtime.hintsBlock);
|
|
242
|
+
// Tell the user once when the host's own patch or shell tool still runs
|
|
243
|
+
// beside AFT's apply_patch or bash. Checked on the first prompt, once
|
|
244
|
+
// every plugin has registered its tools, and shown by AFT's TUI plugin
|
|
245
|
+
// as a toast; nothing is written into the session, which the model reads.
|
|
246
|
+
yield* dependencies.registerHostToolOverlapNotice(
|
|
247
|
+
context,
|
|
248
|
+
new Set(Object.keys(runtime.tools)),
|
|
249
|
+
(message) => rpc.emitHostToolOverlap({ message }),
|
|
250
|
+
);
|
|
215
251
|
});
|
|
216
252
|
};
|
|
217
253
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
1
3
|
import { resolvePromptContext } from "./last-assistant-model.js";
|
|
4
|
+
import { isV2PluginContext } from "./v2-context.js";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Append an `ignored: true` synthetic user message to a session.
|
|
@@ -24,6 +27,21 @@ export async function sendIgnoredMessage(
|
|
|
24
27
|
sessionID: string,
|
|
25
28
|
text: string,
|
|
26
29
|
): Promise<void> {
|
|
30
|
+
if (isV2PluginContext(client)) {
|
|
31
|
+
const context = client as {
|
|
32
|
+
session: {
|
|
33
|
+
synthetic(input: {
|
|
34
|
+
sessionID: string;
|
|
35
|
+
text: string;
|
|
36
|
+
resume: false;
|
|
37
|
+
}): Effect.Effect<unknown, unknown>;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
// V2 owns the current agent/model itself. Synthetic inbox entries neither
|
|
41
|
+
// switch that context nor resume a turn, and must actually run as Effects.
|
|
42
|
+
await Effect.runPromise(context.session.synthetic({ sessionID, text, resume: false }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
27
45
|
const typedClient = client as {
|
|
28
46
|
session?: {
|
|
29
47
|
prompt?: (input: unknown) => unknown;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
1
3
|
import { resolvePromptContext } from "./last-assistant-model.js";
|
|
4
|
+
import { isV2PluginContext } from "./v2-context.js";
|
|
2
5
|
|
|
3
6
|
export const VISION_HOST_TIMEOUT_MS = 1500;
|
|
4
7
|
type Model = { providerID: string; modelID: string };
|
|
@@ -69,12 +72,19 @@ export async function currentSessionVisionCapability(
|
|
|
69
72
|
let capability = state.capabilities.get(key);
|
|
70
73
|
if (!capability) {
|
|
71
74
|
capability = (async () => {
|
|
72
|
-
const api = (client as { provider?: { list?: () =>
|
|
75
|
+
const api = (client as { provider?: { list?: () => unknown } }).provider;
|
|
73
76
|
if (!api?.list) return undefined;
|
|
74
|
-
const list =
|
|
77
|
+
const list = async () => {
|
|
78
|
+
const result = api.list?.();
|
|
79
|
+
return isV2PluginContext(client)
|
|
80
|
+
? Effect.runPromise(result as Effect.Effect<unknown, unknown>)
|
|
81
|
+
: await result;
|
|
82
|
+
};
|
|
75
83
|
const listed = record(await bounded(list));
|
|
76
84
|
const catalog = record(listed?.data) ?? listed;
|
|
77
|
-
const providers =
|
|
85
|
+
const providers = Array.isArray(listed?.data)
|
|
86
|
+
? listed.data
|
|
87
|
+
: (catalog?.all ?? catalog?.providers);
|
|
78
88
|
if (!Array.isArray(providers)) return undefined;
|
|
79
89
|
const provider = providers.map(record).find((entry) => entry?.id === currentModel.providerID);
|
|
80
90
|
const models = provider?.models;
|
|
@@ -83,7 +93,7 @@ export async function currentSessionVisionCapability(
|
|
|
83
93
|
? models.find((entry) => record(entry)?.id === currentModel.modelID)
|
|
84
94
|
: record(models)?.[currentModel.modelID],
|
|
85
95
|
);
|
|
86
|
-
const inputs = record(entry?.modalities)?.input;
|
|
96
|
+
const inputs = record(entry?.capabilities)?.input ?? record(entry?.modalities)?.input;
|
|
87
97
|
return Array.isArray(inputs)
|
|
88
98
|
? inputs.includes("image")
|
|
89
99
|
: typeof entry?.attachment === "boolean"
|
package/src/shared/rpc-client.ts
CHANGED
|
@@ -133,19 +133,46 @@ export class AftRpcClient {
|
|
|
133
133
|
const { signal } = options;
|
|
134
134
|
throwIfAborted(signal);
|
|
135
135
|
|
|
136
|
+
// Fast path: reuse the endpoint that served the last warm response while
|
|
137
|
+
// it is still the newest live port file, skipping discovery's sequential
|
|
138
|
+
// health checks. Anything other than an accepted warm response falls back
|
|
139
|
+
// to the full scan below.
|
|
140
|
+
let placeholder: T | null = null;
|
|
141
|
+
let lastError: unknown = null;
|
|
142
|
+
let answeredPort: number | null = null;
|
|
143
|
+
const cached = this.cachedEndpoint();
|
|
144
|
+
if (cached) {
|
|
145
|
+
try {
|
|
146
|
+
const result = await this.callOne<T>(method, params, cached, signal);
|
|
147
|
+
if (this.looksLikePlaceholder(result)) {
|
|
148
|
+
placeholder = result;
|
|
149
|
+
answeredPort = cached.port;
|
|
150
|
+
} else if (!options.accept || options.accept(result)) {
|
|
151
|
+
return result;
|
|
152
|
+
} else {
|
|
153
|
+
answeredPort = cached.port;
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
throwIfAborted(signal);
|
|
157
|
+
}
|
|
158
|
+
this.port = null;
|
|
159
|
+
this.token = null;
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
// Try ALL discovered ports for this project (OpenCode TUI under --port 0
|
|
137
163
|
// loads our plugin twice in the same process, so two RPC servers listen
|
|
138
|
-
// and we have to try both — only one's bridge is actually warm).
|
|
139
|
-
|
|
140
|
-
|
|
164
|
+
// and we have to try both — only one's bridge is actually warm). A port
|
|
165
|
+
// that already answered on the fast path is not asked again.
|
|
166
|
+
const infos = (await this.resolvePortInfos(signal)).filter(
|
|
167
|
+
(info) => info.port !== answeredPort,
|
|
168
|
+
);
|
|
169
|
+
if (infos.length === 0 && placeholder === null) {
|
|
141
170
|
throw new Error("AFT RPC server not available");
|
|
142
171
|
}
|
|
143
172
|
|
|
144
173
|
// First pass: try every port. Prefer responses that look like "warm
|
|
145
174
|
// bridge" output (i.e. not the synthetic `status: "not_initialized"`
|
|
146
175
|
// placeholder served when this instance's bridge hasn't been spawned).
|
|
147
|
-
let placeholder: T | null = null;
|
|
148
|
-
let lastError: unknown = null;
|
|
149
176
|
for (const info of infos) {
|
|
150
177
|
throwIfAborted(signal);
|
|
151
178
|
try {
|
|
@@ -250,6 +277,20 @@ export class AftRpcClient {
|
|
|
250
277
|
return status === "not_initialized";
|
|
251
278
|
}
|
|
252
279
|
|
|
280
|
+
/**
|
|
281
|
+
* The endpoint that served the last warm response, if it is still the newest
|
|
282
|
+
* live port file for this project. Discovery tries ports newest first and
|
|
283
|
+
* returns the first warm answer, so while this holds the full scan would
|
|
284
|
+
* reach the same server first. Reading the port files also drops entries
|
|
285
|
+
* whose owning process has died, so a dead server is never reused.
|
|
286
|
+
*/
|
|
287
|
+
private cachedEndpoint(): PortInfo | null {
|
|
288
|
+
if (this.port === null) return null;
|
|
289
|
+
const [newest] = this.readAllPortFiles();
|
|
290
|
+
if (!newest || newest.port !== this.port || newest.token !== this.token) return null;
|
|
291
|
+
return newest;
|
|
292
|
+
}
|
|
293
|
+
|
|
253
294
|
/** Check if any RPC server is reachable. */
|
|
254
295
|
async isAvailable(): Promise<boolean> {
|
|
255
296
|
try {
|
package/src/shared/rpc-server.ts
CHANGED
|
@@ -319,6 +319,10 @@ export class AftRpcServer {
|
|
|
319
319
|
return json({ error: "Invalid JSON" }, 400);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
323
|
+
return json({ error: "Expected a JSON object" }, 400);
|
|
324
|
+
}
|
|
325
|
+
|
|
322
326
|
if (!tokensMatch(typeof params.token === "string" ? params.token : "", this.token)) {
|
|
323
327
|
return json({ error: "Forbidden" }, 403);
|
|
324
328
|
}
|
|
@@ -453,6 +457,12 @@ export class AftRpcServer {
|
|
|
453
457
|
return;
|
|
454
458
|
}
|
|
455
459
|
|
|
460
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
461
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
462
|
+
res.end(JSON.stringify({ error: "Expected a JSON object" }));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
456
466
|
if (!tokensMatch(typeof params.token === "string" ? params.token : "", this.token)) {
|
|
457
467
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
458
468
|
res.end(JSON.stringify({ error: "Forbidden" }));
|