@gajae-code/coding-agent 0.11.4 → 0.11.5
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 +23 -0
- package/dist/types/cli/notify-cli.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +20 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
- package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/owner.d.ts +3 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -0
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-transcript-format.d.ts +58 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
- package/dist/types/plan-mode/approved-plan.d.ts +1 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
- package/dist/types/sdk/bus/config-commands.d.ts +8 -0
- package/dist/types/sdk/bus/config.d.ts +22 -0
- package/dist/types/sdk/bus/index.d.ts +162 -2
- package/dist/types/sdk/bus/notification-service.d.ts +71 -8
- package/dist/types/sdk/bus/session-control.d.ts +11 -8
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
- package/dist/types/sdk/session.d.ts +16 -1
- package/dist/types/secrets/index.d.ts +4 -2
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +31 -2
- package/dist/types/session/artifacts.d.ts +30 -8
- package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
- package/dist/types/session/session-manager.d.ts +88 -18
- package/dist/types/session/session-storage.d.ts +27 -0
- package/dist/types/session/streaming-output.d.ts +4 -0
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/tool-discovery/tool-index.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +48 -0
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/index.d.ts +4 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/src/cli/notify-cli.ts +40 -18
- package/src/commands/sdk.ts +7 -2
- package/src/config/settings-schema.ts +17 -0
- package/src/daemon/runtime.ts +2 -2
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
- package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
- package/src/defaults/gjc/skills/team/SKILL.md +7 -9
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
- package/src/eval/py/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/plugins/marketplace/manager.ts +94 -10
- package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
- package/src/gjc-runtime/deep-interview-state.ts +127 -3
- package/src/gjc-runtime/launch-tmux.ts +61 -6
- package/src/gjc-runtime/managed-owner-admission.ts +352 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
- package/src/gjc-runtime/state-runtime.ts +38 -12
- package/src/gjc-runtime/team-runtime.ts +1 -1
- package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
- package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
- package/src/harness-control-plane/owner.ts +59 -3
- package/src/hindsight/client.ts +65 -2
- package/src/internal-urls/agent-protocol.ts +76 -5
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/internal-urls/local-protocol.ts +416 -18
- package/src/lsp/index.ts +4 -88
- package/src/main.ts +37 -13
- package/src/modes/acp/acp-agent.ts +8 -0
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/notifications-settings-editor.ts +26 -2
- package/src/modes/components/session-observer-overlay.ts +40 -24
- package/src/modes/components/settings-selector.ts +3 -1
- package/src/modes/components/tool-transcript-format.ts +321 -0
- package/src/modes/components/transcript-viewer-overlay.ts +39 -17
- package/src/modes/controllers/goal-mode-controller.ts +20 -1
- package/src/modes/controllers/plan-mode-controller.ts +2 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +70 -8
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
- package/src/plan-mode/approved-plan.ts +3 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/lsp.md +3 -3
- package/src/runtime-mcp/manager.ts +16 -1
- package/src/runtime-mcp/oauth-discovery.ts +116 -26
- package/src/sdk/broker/session-index.ts +122 -64
- package/src/sdk/bus/chat-daemon-cli.ts +72 -10
- package/src/sdk/bus/chat-daemon-control.ts +526 -130
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/config-commands.ts +22 -0
- package/src/sdk/bus/config.ts +37 -0
- package/src/sdk/bus/index.ts +384 -92
- package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
- package/src/sdk/bus/notification-service.ts +284 -62
- package/src/sdk/bus/recent-activity.ts +14 -5
- package/src/sdk/bus/session-control.ts +150 -44
- package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
- package/src/sdk/bus/telegram-daemon-control.ts +294 -76
- package/src/sdk/bus/telegram-daemon.ts +1788 -267
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +155 -27
- package/src/secrets/index.ts +53 -10
- package/src/secrets/obfuscator.ts +21 -18
- package/src/secrets/regex.ts +2 -0
- package/src/session/agent-session.ts +495 -253
- package/src/session/artifacts.ts +188 -23
- package/src/session/context-estimation.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +200 -38
- package/src/session/internal/managed-session-storage.ts +1152 -46
- package/src/session/session-manager.ts +1055 -148
- package/src/session/session-storage.ts +109 -11
- package/src/session/streaming-output.ts +6 -0
- package/src/task/executor.ts +71 -26
- package/src/task/index.ts +63 -20
- package/src/tool-discovery/tool-index.ts +18 -0
- package/src/tools/ask.ts +129 -32
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/index.ts +4 -0
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent.ts +2 -0
|
@@ -76,6 +76,13 @@ export interface NotificationGateResolutionOptions {
|
|
|
76
76
|
closeClaimInvalid(reason: string): void;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export class NotificationGatePolicyChangedError extends Error {
|
|
80
|
+
constructor() {
|
|
81
|
+
super("Notification policy changed while resolving the workflow gate");
|
|
82
|
+
this.name = "NotificationGatePolicyChangedError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
79
86
|
export interface AskSelectedAckRecoveryParticipant {
|
|
80
87
|
requestRecoveredAskSelectedAck(input: {
|
|
81
88
|
sessionId: string;
|
|
@@ -361,7 +368,8 @@ export class BrokerWorkflowGateEmitter implements WorkflowGateEmitter {
|
|
|
361
368
|
daemonDeadlineAt: Date.now() + 8_000,
|
|
362
369
|
hostTimeoutMs: 10_000,
|
|
363
370
|
});
|
|
364
|
-
} catch {
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error instanceof NotificationGatePolicyChangedError) throw error;
|
|
365
373
|
outcome = { status: "unknown", reason: "host_timeout" };
|
|
366
374
|
}
|
|
367
375
|
this.#broker.updateAckPolicy(response.gate_id, {
|
|
@@ -378,6 +386,14 @@ export class BrokerWorkflowGateEmitter implements WorkflowGateEmitter {
|
|
|
378
386
|
else closeClaimInvalid(resolution.error?.code ?? "invalid_answer");
|
|
379
387
|
return resolution;
|
|
380
388
|
} catch (error) {
|
|
389
|
+
if (error instanceof NotificationGatePolicyChangedError) {
|
|
390
|
+
try {
|
|
391
|
+
closeClaimInvalid(error.message);
|
|
392
|
+
} finally {
|
|
393
|
+
this.quarantineGate(response.gate_id);
|
|
394
|
+
}
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
381
397
|
closeClaimInvalid(error instanceof Error ? error.message : "invalid_answer");
|
|
382
398
|
this.#scheduleRecovery();
|
|
383
399
|
throw error;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* swapped in later without changing callers.
|
|
13
13
|
*/
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
15
|
+
import { deepInterviewCharacterCount } from "../../../gjc-runtime/deep-interview-state";
|
|
15
16
|
import type { JsonSchema, WorkflowGateValidationError } from "./workflow-gate-types";
|
|
16
17
|
|
|
17
18
|
/** Keywords this wrapper understands. Any other keyword is rejected. */
|
|
@@ -238,7 +239,8 @@ function validateValue(schema: JsonSchema, value: unknown, path: string, errors:
|
|
|
238
239
|
if (!ok) errors.push({ path, keyword: "enum", message: "value not in enum", expected: schema.enum });
|
|
239
240
|
}
|
|
240
241
|
if (typeof value === "string") {
|
|
241
|
-
|
|
242
|
+
const characterCount = deepInterviewCharacterCount(value);
|
|
243
|
+
if (schema.minLength !== undefined && characterCount < schema.minLength) {
|
|
242
244
|
errors.push({
|
|
243
245
|
path,
|
|
244
246
|
keyword: "minLength",
|
|
@@ -246,7 +248,7 @@ function validateValue(schema: JsonSchema, value: unknown, path: string, errors:
|
|
|
246
248
|
expected: schema.minLength,
|
|
247
249
|
});
|
|
248
250
|
}
|
|
249
|
-
if (schema.maxLength !== undefined &&
|
|
251
|
+
if (schema.maxLength !== undefined && characterCount > schema.maxLength) {
|
|
250
252
|
errors.push({
|
|
251
253
|
path,
|
|
252
254
|
keyword: "maxLength",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { deepInterviewCharacterCount } from "../../../gjc-runtime/deep-interview-state";
|
|
2
|
+
|
|
1
3
|
/** Transport-neutral workflow gate contract used by SDK workflow coordination. */
|
|
2
4
|
|
|
3
5
|
/** Lifecycle stages that emit machine-addressable SDK workflow gates. */
|
|
@@ -14,12 +16,28 @@ export type WorkflowGateKind = "question" | "approval" | "execution";
|
|
|
14
16
|
/** "Other (type your own)" sentinel, mirroring the interactive ask tool. */
|
|
15
17
|
export const GATE_OTHER_OPTION = "Other (type your own)";
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Structured adapter context queued at interview start. Non-behavioral:
|
|
21
|
+
* `confused_terms` and `references` MUST NOT alter the first question, are never
|
|
22
|
+
* inferred from vocabulary density, and referenced `url`/`excerpt` are inert
|
|
23
|
+
* strings that are never auto-fetched.
|
|
24
|
+
*/
|
|
25
|
+
export interface AskGateReference {
|
|
26
|
+
reference_id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
origin: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
excerpt?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
export interface AskGateDeepInterviewState {
|
|
18
34
|
round_id?: string;
|
|
19
35
|
round: number;
|
|
20
36
|
component: string;
|
|
21
37
|
dimension: string;
|
|
22
38
|
ambiguity: number;
|
|
39
|
+
confused_terms?: string[];
|
|
40
|
+
references?: AskGateReference[];
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
export interface AskGateStageStateInput {
|
|
@@ -34,10 +52,90 @@ export interface AskGateStageStateInput {
|
|
|
34
52
|
export interface AskGateSchemaInput {
|
|
35
53
|
multi?: boolean;
|
|
36
54
|
allowEmpty?: boolean;
|
|
55
|
+
customMaxLength?: number;
|
|
37
56
|
}
|
|
38
57
|
|
|
39
58
|
function isBoundedAskGateString(value: unknown): value is string {
|
|
40
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
typeof value === "string" &&
|
|
61
|
+
value.length > 0 &&
|
|
62
|
+
deepInterviewCharacterCount(value) <= MAX_ASK_GATE_ADAPTER_STRING_LENGTH
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Max characters for adapter metadata identifiers and confused terms. */
|
|
67
|
+
const MAX_ASK_GATE_ADAPTER_STRING_LENGTH = 256;
|
|
68
|
+
const MAX_ASK_GATE_DEEP_INTERVIEW_STRING_LENGTH = 128;
|
|
69
|
+
/** Max characters for a longer inert reference string (url/excerpt); never fetched. */
|
|
70
|
+
const MAX_ASK_GATE_LONG_STRING_LENGTH = 2048;
|
|
71
|
+
const MAX_ASK_GATE_CONFUSED_TERMS = 32;
|
|
72
|
+
const MAX_ASK_GATE_REFERENCES = 32;
|
|
73
|
+
|
|
74
|
+
function isBoundedAskGateAdapterString(value: unknown): value is string {
|
|
75
|
+
return (
|
|
76
|
+
typeof value === "string" &&
|
|
77
|
+
value.length > 0 &&
|
|
78
|
+
deepInterviewCharacterCount(value) <= MAX_ASK_GATE_ADAPTER_STRING_LENGTH
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isBoundedAskGateDeepInterviewString(value: unknown): value is string {
|
|
83
|
+
return (
|
|
84
|
+
typeof value === "string" &&
|
|
85
|
+
value.length > 0 &&
|
|
86
|
+
deepInterviewCharacterCount(value) <= MAX_ASK_GATE_DEEP_INTERVIEW_STRING_LENGTH
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isBoundedAskGateLongString(value: unknown): value is string {
|
|
91
|
+
return (
|
|
92
|
+
typeof value === "string" &&
|
|
93
|
+
value.length > 0 &&
|
|
94
|
+
deepInterviewCharacterCount(value) <= MAX_ASK_GATE_LONG_STRING_LENGTH
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isDenseArray(value: unknown[]): boolean {
|
|
99
|
+
for (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index)) return false;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isValidAskGateConfusedTerms(value: unknown): value is string[] {
|
|
104
|
+
return (
|
|
105
|
+
Array.isArray(value) &&
|
|
106
|
+
isDenseArray(value) &&
|
|
107
|
+
value.length <= MAX_ASK_GATE_CONFUSED_TERMS &&
|
|
108
|
+
value.every(isBoundedAskGateAdapterString)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isValidAskGateReference(value: unknown): boolean {
|
|
113
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
114
|
+
const prototype = Object.getPrototypeOf(value);
|
|
115
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
116
|
+
const ref = value as Record<string, unknown>;
|
|
117
|
+
const allowed = new Set(["reference_id", "label", "origin", "url", "excerpt"]);
|
|
118
|
+
if (Object.keys(ref).some(key => !allowed.has(key))) return false;
|
|
119
|
+
if (!Object.hasOwn(ref, "reference_id") || !Object.hasOwn(ref, "label") || !Object.hasOwn(ref, "origin"))
|
|
120
|
+
return false;
|
|
121
|
+
if (
|
|
122
|
+
!isBoundedAskGateAdapterString(ref.reference_id) ||
|
|
123
|
+
!isBoundedAskGateAdapterString(ref.label) ||
|
|
124
|
+
!isBoundedAskGateAdapterString(ref.origin)
|
|
125
|
+
)
|
|
126
|
+
return false;
|
|
127
|
+
if (ref.url !== undefined && !isBoundedAskGateLongString(ref.url)) return false;
|
|
128
|
+
if (ref.excerpt !== undefined && !isBoundedAskGateLongString(ref.excerpt)) return false;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isValidAskGateReferences(value: unknown): boolean {
|
|
133
|
+
return (
|
|
134
|
+
Array.isArray(value) &&
|
|
135
|
+
isDenseArray(value) &&
|
|
136
|
+
value.length <= MAX_ASK_GATE_REFERENCES &&
|
|
137
|
+
value.every(isValidAskGateReference)
|
|
138
|
+
);
|
|
41
139
|
}
|
|
42
140
|
|
|
43
141
|
/** Rejects stage state that is not exactly compatible with the generic ask-gate producer. */
|
|
@@ -62,6 +160,8 @@ export function validateAskGateStageState(value: unknown): asserts value is Reco
|
|
|
62
160
|
"challenge_mode",
|
|
63
161
|
"ambiguity",
|
|
64
162
|
"topology_gate",
|
|
163
|
+
"confused_terms",
|
|
164
|
+
"references",
|
|
65
165
|
]);
|
|
66
166
|
if (Object.keys(state).some(key => !allowed.has(key))) throw new Error("ask gate stage_state has an unknown field");
|
|
67
167
|
if (
|
|
@@ -76,7 +176,10 @@ export function validateAskGateStageState(value: unknown): asserts value is Reco
|
|
|
76
176
|
throw new Error("ask gate stage_state has invalid required fields");
|
|
77
177
|
if (state.navigation_label !== undefined && state.navigation_label !== "Next" && state.navigation_label !== "Done")
|
|
78
178
|
throw new Error("ask gate stage_state has an invalid navigation label");
|
|
79
|
-
for (const key of ["round_id", "component", "dimension"
|
|
179
|
+
for (const key of ["round_id", "component", "dimension"])
|
|
180
|
+
if (state[key] !== undefined && !isBoundedAskGateDeepInterviewString(state[key]))
|
|
181
|
+
throw new Error(`ask gate stage_state has an invalid ${key}`);
|
|
182
|
+
for (const key of ["mode", "challenge_mode"])
|
|
80
183
|
if (state[key] !== undefined && !isBoundedAskGateString(state[key]))
|
|
81
184
|
throw new Error(`ask gate stage_state has an invalid ${key}`);
|
|
82
185
|
if (state.deep_interview_metadata !== undefined && typeof state.deep_interview_metadata !== "boolean")
|
|
@@ -93,6 +196,10 @@ export function validateAskGateStageState(value: unknown): asserts value is Reco
|
|
|
93
196
|
throw new Error("ask gate stage_state has an invalid ambiguity");
|
|
94
197
|
if (state.topology_gate !== undefined && typeof state.topology_gate !== "boolean")
|
|
95
198
|
throw new Error("ask gate stage_state has an invalid topology flag");
|
|
199
|
+
if (state.confused_terms !== undefined && !isValidAskGateConfusedTerms(state.confused_terms))
|
|
200
|
+
throw new Error("ask gate stage_state has invalid confused_terms");
|
|
201
|
+
if (state.references !== undefined && !isValidAskGateReferences(state.references))
|
|
202
|
+
throw new Error("ask gate stage_state has invalid references");
|
|
96
203
|
}
|
|
97
204
|
|
|
98
205
|
/** Builds the canonical generic ask answer schema. */
|
|
@@ -114,6 +221,7 @@ export function buildAskGateAnswerSchema(question: AskGateSchemaInput, labels: s
|
|
|
114
221
|
custom: {
|
|
115
222
|
type: "string",
|
|
116
223
|
minLength: 1,
|
|
224
|
+
...(question.customMaxLength === undefined ? {} : { maxLength: question.customMaxLength }),
|
|
117
225
|
pattern: "\\S",
|
|
118
226
|
description: "free-text answer; required when `other` is true",
|
|
119
227
|
},
|
|
@@ -142,7 +250,12 @@ export function buildAskGateAnswerSchema(question: AskGateSchemaInput, labels: s
|
|
|
142
250
|
properties: {
|
|
143
251
|
selected: selectedWithOther,
|
|
144
252
|
other: { const: true },
|
|
145
|
-
custom: {
|
|
253
|
+
custom: {
|
|
254
|
+
type: "string",
|
|
255
|
+
minLength: 1,
|
|
256
|
+
...(question.customMaxLength === undefined ? {} : { maxLength: question.customMaxLength }),
|
|
257
|
+
pattern: "\\S",
|
|
258
|
+
},
|
|
146
259
|
action: { const: "answer" },
|
|
147
260
|
},
|
|
148
261
|
required: ["selected", "other", "custom"],
|
|
@@ -176,6 +289,10 @@ export function buildAskGateStageState(input: AskGateStageStateInput, labels: st
|
|
|
176
289
|
dimension: input.deepInterview.dimension,
|
|
177
290
|
ambiguity: input.deepInterview.ambiguity,
|
|
178
291
|
...(input.deepInterview.round_id === undefined ? {} : { round_id: input.deepInterview.round_id }),
|
|
292
|
+
...(input.deepInterview.confused_terms === undefined
|
|
293
|
+
? {}
|
|
294
|
+
: { confused_terms: input.deepInterview.confused_terms }),
|
|
295
|
+
...(input.deepInterview.references === undefined ? {} : { references: input.deepInterview.references }),
|
|
179
296
|
}
|
|
180
297
|
: (input.fallbackState ?? {})),
|
|
181
298
|
};
|
|
@@ -114,6 +114,7 @@ interface RenameApprovedPlanFileOptions {
|
|
|
114
114
|
planFilePath: string;
|
|
115
115
|
finalPlanFilePath: string;
|
|
116
116
|
getArtifactsDir: () => string | null;
|
|
117
|
+
isManagedDestination?: () => boolean;
|
|
117
118
|
getSessionId: () => string | null;
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -124,12 +125,13 @@ function assertLocalUrl(path: string, label: "source" | "destination"): void {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
export async function renameApprovedPlanFile(options: RenameApprovedPlanFileOptions): Promise<void> {
|
|
127
|
-
const { planFilePath, finalPlanFilePath, getArtifactsDir, getSessionId } = options;
|
|
128
|
+
const { planFilePath, finalPlanFilePath, getArtifactsDir, getSessionId, isManagedDestination } = options;
|
|
128
129
|
assertLocalUrl(planFilePath, "source");
|
|
129
130
|
assertLocalUrl(finalPlanFilePath, "destination");
|
|
130
131
|
|
|
131
132
|
const resolveOptions = {
|
|
132
133
|
getArtifactsDir: () => getArtifactsDir(),
|
|
134
|
+
isManagedDestination,
|
|
133
135
|
getSessionId: () => getSessionId(),
|
|
134
136
|
};
|
|
135
137
|
const resolvedSource = resolveLocalUrlToPath(normalizeLocalScheme(planFilePath), resolveOptions);
|
|
@@ -33,6 +33,7 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
33
33
|
- Never approve CRITICAL or HIGH severity issues.
|
|
34
34
|
- Do not skip spec compliance to jump to style nitpicks.
|
|
35
35
|
- Be constructive: explain why an issue matters and how to fix it or strengthen the design.
|
|
36
|
+
- Practical sufficiency beats endless logical completeness: treat preemptive logic for edge cases with no observed or evidenced trigger as over-engineering, but never downgrade genuine CRITICAL/HIGH architecture, extensibility, or robustness risks that have observability-based evidence.
|
|
36
37
|
</constraints>
|
|
37
38
|
|
|
38
39
|
<review_stages>
|
|
@@ -23,6 +23,7 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
23
23
|
- Do not invent problems; report no issues found when the plan passes.
|
|
24
24
|
- Escalate routing needs upward: planner for plan revision, the deep-interview skill for requirements gathering, architect for code analysis.
|
|
25
25
|
- For consensus planning, reject shallow alternatives, driver contradictions, vague risks, weak verification, missing acceptance criteria, or under-specified areas needing expansion before execution.
|
|
26
|
+
- Prefer practical sufficiency over endless logical completeness: do not demand preemptive logic for edge cases that are not actually observed or evidenced, yet still require extensibility and robustness when observability-based evidence shows the investment is worth the token and time.
|
|
26
27
|
</constraints>
|
|
27
28
|
|
|
28
29
|
<execution_loop>
|
|
@@ -37,6 +37,6 @@ Rules are local constraints. You MUST read `rule://<name>` when working in that
|
|
|
37
37
|
{{/if}}
|
|
38
38
|
{{#if secretsEnabled}}
|
|
39
39
|
<redacted-content>
|
|
40
|
-
Some values in tool output are redacted for security. They appear as `#
|
|
40
|
+
Some values in tool output are redacted for security. They appear as versioned `#GJC1_…#` tokens. These are **not errors** — they are intentional placeholders for sensitive values (API keys, passwords, tokens). Treat them as opaque strings. Do not attempt to decode, fix, or report them as problems.
|
|
41
41
|
</redacted-content>
|
|
42
42
|
{{/if}}
|
|
@@ -94,7 +94,7 @@ Discoverable capabilities include browser automation, scheduling, debugging, and
|
|
|
94
94
|
|
|
95
95
|
{{#if secretsEnabled}}
|
|
96
96
|
<redacted-content>
|
|
97
|
-
Some tool output values are intentionally redacted as `#
|
|
97
|
+
Some tool output values are intentionally redacted as versioned `#GJC1_…#` tokens. Treat them as opaque sensitive strings.
|
|
98
98
|
</redacted-content>
|
|
99
99
|
{{/if}}
|
|
100
100
|
|
|
@@ -206,6 +206,7 @@ Guardrails? None. Restrictions? None. Freedom? Unlimited.
|
|
|
206
206
|
- **Do your homework first** - Read the files, search, and understand the context before asking
|
|
207
207
|
- **Don’t Stop at Analysis** - Once you’ve assessed the situation, take the next step (session, comment, merge, fix, cleanup) on your own
|
|
208
208
|
- **No Inflating Operational/Meta Issues** - Just because the backlog is empty doesn’t mean you should force operational follow-ups, verifiability checks, or meta issues. Only raise issues for actual repository problems; otherwise, stop.
|
|
209
|
+
- **Practical sufficiency over completeness** - Don’t add logic that preemptively handles edge cases you haven’t actually observed; endless logical completeness loses to practical sufficiency. Extensibility and robustness matter, but they must be observability-based and evidence-based—any engineering without underlying evidence that it’s worth the token and time is over-engineering.
|
|
209
210
|
- **Always maintain execution** - Do not end your turn with just reporting, summarizing, or analyzing. If an action is possible, execute it immediately during the same turn.
|
|
210
211
|
- **Always be in action mode** - The default stance is intervention, not observation. If you see something that needs cleaning up, clean it up; if you see a session that needs opening, open it; if you see a PR that needs blocking, block it; if you see a PR that needs merging, review it and merge it.
|
|
211
212
|
- **No begging for permission** - For obvious follow-up actions (cleaning up sessions, marking items as stale, fixing routing, updating documentation to reflect Doctrine), don’t ask—just do it.
|
package/src/prompts/tools/lsp.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Interacts with Language Server Protocol servers for code intelligence.
|
|
2
2
|
|
|
3
3
|
<operations>
|
|
4
|
-
- `diagnostics`: Get errors/warnings for a file
|
|
4
|
+
- `diagnostics`: Get errors/warnings for a concrete file or a glob of files
|
|
5
5
|
- `definition`: Go to symbol definition → file path + position + 3-line source context
|
|
6
6
|
- `type_definition`: Go to symbol type definition → file path + position + 3-line source context
|
|
7
7
|
- `implementation`: Find concrete implementations → file path + position + 3-line source context
|
|
@@ -18,7 +18,7 @@ Interacts with Language Server Protocol servers for code intelligence.
|
|
|
18
18
|
</operations>
|
|
19
19
|
|
|
20
20
|
<parameters>
|
|
21
|
-
- `file`: File path, glob pattern (e.g. `src/**/*.ts`), or `"*"` for workspace scope. Globs are expanded locally before dispatch. `"*"` routes `
|
|
21
|
+
- `file`: File path, glob pattern (e.g. `src/**/*.ts`), or `"*"` for workspace scope where supported. Globs are expanded locally before dispatch. `"*"` routes `symbols`/`reload` to their workspace-wide form; workspace build diagnostics are unavailable through `lsp`.
|
|
22
22
|
- `line`: 1-indexed line number for position-based actions
|
|
23
23
|
- `symbol`: Substring on the target line used to resolve column automatically. Append `#N` to pick the Nth occurrence on that line (1-indexed; default 1) — e.g. `foo#2` selects the second `foo`.
|
|
24
24
|
- `query`: Symbol search query, code-action kind filter / selector (list/apply mode), or LSP method name when `action: request`
|
|
@@ -31,7 +31,7 @@ Interacts with Language Server Protocol servers for code intelligence.
|
|
|
31
31
|
<caution>
|
|
32
32
|
- Requires running LSP server for target language
|
|
33
33
|
- Some operations require file to be saved to disk
|
|
34
|
-
- Glob expansion samples up to 20 files per request;
|
|
34
|
+
- Glob expansion samples up to 20 files per request; narrow broad patterns when they exceed that limit
|
|
35
35
|
- When `symbol` is provided for position-based actions, missing symbols or out-of-bounds `#N` occurrence selectors return an explicit error instead of silently falling back
|
|
36
36
|
</caution>
|
|
37
37
|
|
|
@@ -235,6 +235,7 @@ export class MCPManager {
|
|
|
235
235
|
#epoch = 0;
|
|
236
236
|
readonly #toolsOnly: boolean;
|
|
237
237
|
#toolsOnlyConfigLoaded = false;
|
|
238
|
+
#connectionSetSealed = false;
|
|
238
239
|
|
|
239
240
|
#serverError(message: string): string {
|
|
240
241
|
return this.#toolsOnly ? "MCP server unavailable" : message;
|
|
@@ -242,6 +243,17 @@ export class MCPManager {
|
|
|
242
243
|
#assertRawMCPAccessAllowed(): void {
|
|
243
244
|
if (this.#toolsOnly) throw new Error("Tools-only MCP manager does not allow raw MCP access");
|
|
244
245
|
}
|
|
246
|
+
#assertConnectionSetMutable(): void {
|
|
247
|
+
if (this.#connectionSetSealed) throw new Error("MCP manager connection set is sealed");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
sealConnectionSet(): void {
|
|
251
|
+
this.#connectionSetSealed = true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
isConnectionSetSealed(): boolean {
|
|
255
|
+
return this.#connectionSetSealed;
|
|
256
|
+
}
|
|
245
257
|
|
|
246
258
|
#isCurrentConnection(
|
|
247
259
|
name: string,
|
|
@@ -379,6 +391,7 @@ export class MCPManager {
|
|
|
379
391
|
* Returns tools and any connection errors.
|
|
380
392
|
*/
|
|
381
393
|
async discoverAndConnect(options?: MCPDiscoverOptions): Promise<MCPLoadResult> {
|
|
394
|
+
this.#assertConnectionSetMutable();
|
|
382
395
|
const hasConfigPath = options?.configPath !== undefined;
|
|
383
396
|
if (this.#toolsOnly !== hasConfigPath) {
|
|
384
397
|
throw new Error(
|
|
@@ -414,6 +427,7 @@ export class MCPManager {
|
|
|
414
427
|
onConnecting?: (serverNames: string[]) => void,
|
|
415
428
|
): Promise<MCPLoadResult> {
|
|
416
429
|
this.#assertRawMCPAccessAllowed();
|
|
430
|
+
this.#assertConnectionSetMutable();
|
|
417
431
|
return this.#connectServers(configs, sources, onConnecting);
|
|
418
432
|
}
|
|
419
433
|
|
|
@@ -923,6 +937,7 @@ export class MCPManager {
|
|
|
923
937
|
*/
|
|
924
938
|
async disconnectServer(name: string): Promise<void> {
|
|
925
939
|
this.#assertRawMCPAccessAllowed();
|
|
940
|
+
this.#assertConnectionSetMutable();
|
|
926
941
|
await this.#disconnectServer(name);
|
|
927
942
|
}
|
|
928
943
|
|
|
@@ -1050,7 +1065,7 @@ export class MCPManager {
|
|
|
1050
1065
|
* Returns the new connection, or null if reconnection failed.
|
|
1051
1066
|
*/
|
|
1052
1067
|
async reconnectServer(name: string): Promise<MCPServerConnection | null> {
|
|
1053
|
-
if (this.#toolsOnly) return null;
|
|
1068
|
+
if (this.#toolsOnly || this.#connectionSetSealed) return null;
|
|
1054
1069
|
const pending = this.#pendingReconnections.get(name);
|
|
1055
1070
|
if (pending) return pending;
|
|
1056
1071
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Automatically detects OAuth requirements from MCP server responses
|
|
5
5
|
* and extracts authentication endpoints.
|
|
6
6
|
*/
|
|
7
|
+
import type { AddressResolver, PublicUrlAccepted } from "../web/insane/url-guard";
|
|
8
|
+
import { validatePublicHttpUrl } from "../web/insane/url-guard";
|
|
7
9
|
|
|
8
10
|
export interface OAuthEndpoints {
|
|
9
11
|
authorizationUrl: string;
|
|
@@ -20,6 +22,16 @@ export interface AuthDetectionResult {
|
|
|
20
22
|
message?: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
interface OAuthDiscoveryOptions {
|
|
26
|
+
fetch?: (input: string | URL | Request, init?: BunFetchRequestInit) => Promise<Response>;
|
|
27
|
+
resolver?: AddressResolver;
|
|
28
|
+
maxRequests?: number;
|
|
29
|
+
maxNodes?: number;
|
|
30
|
+
maxRedirects?: number;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
function parseMcpAuthServerUrl(errorMessage: string): string | undefined {
|
|
24
36
|
const match = errorMessage.match(/Mcp-Auth-Server:\s*([^;\]\s]+)/i);
|
|
25
37
|
if (!match?.[1]) return undefined;
|
|
@@ -194,17 +206,11 @@ export function analyzeAuthError(error: Error): AuthDetectionResult {
|
|
|
194
206
|
return { requiresAuth: false };
|
|
195
207
|
}
|
|
196
208
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Try to extract OAuth endpoints
|
|
200
|
-
const oauth = extractOAuthEndpoints(error);
|
|
201
|
-
|
|
202
|
-
if (oauth) {
|
|
209
|
+
// Error text is useful for classification, but is not trusted discovery authority.
|
|
210
|
+
if (extractOAuthEndpoints(error) || extractMcpAuthServerUrl(error)) {
|
|
203
211
|
return {
|
|
204
212
|
requiresAuth: true,
|
|
205
213
|
authType: "oauth",
|
|
206
|
-
oauth,
|
|
207
|
-
authServerUrl,
|
|
208
214
|
message: "Server requires OAuth authentication. Launching authorization flow...",
|
|
209
215
|
};
|
|
210
216
|
}
|
|
@@ -220,7 +226,6 @@ export function analyzeAuthError(error: Error): AuthDetectionResult {
|
|
|
220
226
|
return {
|
|
221
227
|
requiresAuth: true,
|
|
222
228
|
authType: "apikey",
|
|
223
|
-
authServerUrl,
|
|
224
229
|
message: "Server requires API key authentication.",
|
|
225
230
|
};
|
|
226
231
|
}
|
|
@@ -229,7 +234,6 @@ export function analyzeAuthError(error: Error): AuthDetectionResult {
|
|
|
229
234
|
return {
|
|
230
235
|
requiresAuth: true,
|
|
231
236
|
authType: "unknown",
|
|
232
|
-
authServerUrl,
|
|
233
237
|
message: "Server requires authentication but type could not be determined.",
|
|
234
238
|
};
|
|
235
239
|
}
|
|
@@ -241,6 +245,7 @@ export function analyzeAuthError(error: Error): AuthDetectionResult {
|
|
|
241
245
|
export async function discoverOAuthEndpoints(
|
|
242
246
|
serverUrl: string,
|
|
243
247
|
authServerUrl?: string,
|
|
248
|
+
options: OAuthDiscoveryOptions = {},
|
|
244
249
|
): Promise<OAuthEndpoints | null> {
|
|
245
250
|
const wellKnownPaths = [
|
|
246
251
|
"/.well-known/oauth-authorization-server",
|
|
@@ -250,8 +255,82 @@ export async function discoverOAuthEndpoints(
|
|
|
250
255
|
"/.mcp/auth",
|
|
251
256
|
"/authorize", // Some MCP servers expose OAuth config here
|
|
252
257
|
];
|
|
253
|
-
const
|
|
254
|
-
const
|
|
258
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
259
|
+
const maxRequests = options.maxRequests ?? 32;
|
|
260
|
+
const maxNodes = options.maxNodes ?? 8;
|
|
261
|
+
const maxRedirects = options.maxRedirects ?? 3;
|
|
262
|
+
const timeoutSignal = AbortSignal.timeout(options.timeoutMs ?? 10_000);
|
|
263
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
|
|
264
|
+
const { promise: aborted, resolve: resolveAbort } = Promise.withResolvers<null>();
|
|
265
|
+
if (signal.aborted) resolveAbort(null);
|
|
266
|
+
else signal.addEventListener("abort", () => resolveAbort(null), { once: true });
|
|
267
|
+
const queue: string[] = [];
|
|
268
|
+
const queued = new Set<string>();
|
|
269
|
+
const visited = new Set<string>();
|
|
270
|
+
let requests = 0;
|
|
271
|
+
|
|
272
|
+
const canonicalUrl = (raw: string): string | null => {
|
|
273
|
+
try {
|
|
274
|
+
const url = new URL(raw);
|
|
275
|
+
url.hash = "";
|
|
276
|
+
return url.toString();
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const validateUrl = async (raw: string): Promise<PublicUrlAccepted | null> => {
|
|
283
|
+
const canonical = canonicalUrl(raw);
|
|
284
|
+
if (!canonical || signal.aborted) return null;
|
|
285
|
+
const result = await Promise.race([validatePublicHttpUrl(canonical, { resolver: options.resolver }), aborted]);
|
|
286
|
+
if (!result) return null;
|
|
287
|
+
return result.ok && !signal.aborted ? result : null;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const enqueue = async (raw: string): Promise<void> => {
|
|
291
|
+
const canonical = canonicalUrl(raw);
|
|
292
|
+
if (!canonical) return;
|
|
293
|
+
const authority = new URL(canonical).origin;
|
|
294
|
+
if (queued.has(authority) || visited.has(authority) || queued.size + visited.size >= maxNodes) return;
|
|
295
|
+
const accepted = await validateUrl(canonical);
|
|
296
|
+
if (!accepted) return;
|
|
297
|
+
queued.add(accepted.url.origin);
|
|
298
|
+
queue.push(accepted.url.origin);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const fetchMetadata = async (raw: string): Promise<Record<string, unknown> | null> => {
|
|
302
|
+
let current = raw;
|
|
303
|
+
for (let redirects = 0; redirects <= maxRedirects; redirects++) {
|
|
304
|
+
if (signal.aborted || requests >= maxRequests) return null;
|
|
305
|
+
const accepted = await validateUrl(current);
|
|
306
|
+
if (!accepted) return null;
|
|
307
|
+
const target = new URL(accepted.url);
|
|
308
|
+
const address = accepted.addresses[0];
|
|
309
|
+
target.hostname = address.includes(":") ? `[${address}]` : address;
|
|
310
|
+
requests++;
|
|
311
|
+
const response = await fetcher(target, {
|
|
312
|
+
method: "GET",
|
|
313
|
+
headers: { Accept: "application/json", Host: accepted.url.host },
|
|
314
|
+
redirect: "manual",
|
|
315
|
+
signal,
|
|
316
|
+
tls: accepted.url.protocol === "https:" ? { serverName: accepted.url.hostname } : undefined,
|
|
317
|
+
});
|
|
318
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
319
|
+
const location = response.headers.get("Location");
|
|
320
|
+
if (!location || redirects === maxRedirects) return null;
|
|
321
|
+
current = new URL(location, accepted.url).toString();
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!response.ok) return null;
|
|
325
|
+
return (await Promise.race([response.json(), aborted])) as Record<string, unknown> | null;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const server = await validateUrl(serverUrl);
|
|
331
|
+
if (!server) return null;
|
|
332
|
+
await enqueue(server.url.toString());
|
|
333
|
+
if (authServerUrl) await enqueue(authServerUrl);
|
|
255
334
|
|
|
256
335
|
const findEndpoints = (metadata: Record<string, unknown>): OAuthEndpoints | null => {
|
|
257
336
|
if (metadata.authorization_endpoint && metadata.token_endpoint) {
|
|
@@ -310,32 +389,43 @@ export async function discoverOAuthEndpoints(
|
|
|
310
389
|
return null;
|
|
311
390
|
};
|
|
312
391
|
|
|
313
|
-
|
|
314
|
-
|
|
392
|
+
while (queue.length > 0 && !signal.aborted && requests < maxRequests) {
|
|
393
|
+
const baseUrl = queue.shift()!;
|
|
394
|
+
queued.delete(baseUrl);
|
|
395
|
+
if (visited.has(baseUrl)) continue;
|
|
396
|
+
visited.add(baseUrl);
|
|
315
397
|
for (const path of wellKnownPaths) {
|
|
316
398
|
try {
|
|
317
399
|
const url = new URL(path, baseUrl);
|
|
318
|
-
const
|
|
319
|
-
method: "GET",
|
|
320
|
-
headers: { Accept: "application/json" },
|
|
321
|
-
});
|
|
400
|
+
const metadata = await fetchMetadata(url.toString());
|
|
322
401
|
|
|
323
|
-
if (
|
|
324
|
-
const metadata = (await response.json()) as Record<string, unknown>;
|
|
402
|
+
if (metadata) {
|
|
325
403
|
const endpoints = findEndpoints(metadata);
|
|
326
|
-
if (endpoints)
|
|
404
|
+
if (endpoints) {
|
|
405
|
+
const issuer = typeof metadata.issuer === "string" ? canonicalUrl(metadata.issuer) : null;
|
|
406
|
+
const issuerBound = issuer === canonicalUrl(baseUrl);
|
|
407
|
+
const authorization = await validateUrl(endpoints.authorizationUrl);
|
|
408
|
+
const token = await validateUrl(endpoints.tokenUrl);
|
|
409
|
+
const legacySameOrigin =
|
|
410
|
+
!issuer && authorization?.url.origin === baseUrl && token?.url.origin === baseUrl;
|
|
411
|
+
if ((issuerBound || legacySameOrigin) && authorization && token) {
|
|
412
|
+
return {
|
|
413
|
+
...endpoints,
|
|
414
|
+
authorizationUrl: authorization.url.toString(),
|
|
415
|
+
tokenUrl: token.url.toString(),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
327
419
|
|
|
328
420
|
if (path === "/.well-known/oauth-protected-resource") {
|
|
421
|
+
const resource = typeof metadata.resource === "string" ? canonicalUrl(metadata.resource) : null;
|
|
422
|
+
if (resource !== canonicalUrl(server.url.toString())) continue;
|
|
329
423
|
const authServers = Array.isArray(metadata.authorization_servers)
|
|
330
424
|
? metadata.authorization_servers.filter((entry): entry is string => typeof entry === "string")
|
|
331
425
|
: [];
|
|
332
426
|
|
|
333
427
|
for (const discoveredAuthServer of authServers) {
|
|
334
|
-
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
const discovered = await discoverOAuthEndpoints(serverUrl, discoveredAuthServer);
|
|
338
|
-
if (discovered) return discovered;
|
|
428
|
+
await enqueue(discoveredAuthServer);
|
|
339
429
|
}
|
|
340
430
|
}
|
|
341
431
|
}
|