@gajae-code/coding-agent 0.15.4 → 0.15.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 +20 -1
- package/dist/types/cli/update-cli.d.ts +25 -5
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
- package/dist/types/sdk/cli/session-cli.d.ts +42 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/session-runtime.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +4 -3
- package/dist/types/tools/path-utils.d.ts +5 -6
- package/dist/types/tools/tmux-self-injection-guard.d.ts +15 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +102 -1
- package/src/commands/sdk.ts +5 -0
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +10 -4
- package/src/coordinator-mcp/policy.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/launch-worktree.ts +44 -7
- package/src/gjc-runtime/ralplan-runtime.ts +14 -1
- package/src/gjc-runtime/tmux-sessions.ts +145 -48
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/modes/prompt-action-autocomplete.ts +26 -47
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/kind-aware-reconciliation.ts +10 -2
- package/src/sdk/bus/prompt-reconciliation.ts +5 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
- package/src/sdk/cli/session-cli.ts +149 -4
- package/src/sdk/host/host.ts +7 -0
- package/src/sdk/host/session-runtime.test.ts +821 -22
- package/src/sdk/host/session-runtime.ts +669 -67
- package/src/sdk/router/session-router.ts +69 -15
- package/src/session/agent-session.ts +232 -15
- package/src/session/internal/managed-session-storage.ts +64 -26
- package/src/session/session-manager.ts +26 -8
- package/src/tools/bash.ts +14 -0
- package/src/tools/path-utils.ts +77 -29
- package/src/tools/tmux-self-injection-guard.ts +359 -0
|
@@ -242,9 +242,8 @@ export interface ManagedFileSnapshot {
|
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
244
|
/**
|
|
245
|
-
* Descriptor identity of one managed file object.
|
|
246
|
-
* content
|
|
247
|
-
* re-reading the whole payload.
|
|
245
|
+
* Descriptor identity of one managed file object. Append receipts include a
|
|
246
|
+
* content digest when the append implementation has already computed it.
|
|
248
247
|
*/
|
|
249
248
|
export interface ManagedFileIdentity {
|
|
250
249
|
dev: bigint;
|
|
@@ -253,6 +252,7 @@ export interface ManagedFileIdentity {
|
|
|
253
252
|
size: number;
|
|
254
253
|
mtimeNs: bigint;
|
|
255
254
|
ctimeNs: bigint;
|
|
255
|
+
sha256?: string;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/**
|
|
@@ -281,6 +281,7 @@ function sameManagedIdentity(left: ManagedFileIdentity, right: ManagedFileIdenti
|
|
|
281
281
|
export interface ManagedBoundedAppendExpectation {
|
|
282
282
|
readonly dev: string;
|
|
283
283
|
readonly ino: string;
|
|
284
|
+
readonly nlink: string;
|
|
284
285
|
readonly size: string;
|
|
285
286
|
readonly mtimeNs: string;
|
|
286
287
|
readonly ctimeNs: string;
|
|
@@ -295,6 +296,7 @@ function managedFileIdentityFromNative(identity: RecoveryFsIdentity): ManagedFil
|
|
|
295
296
|
size: Number(identity.size),
|
|
296
297
|
mtimeNs: BigInt(identity.mtimeNs),
|
|
297
298
|
ctimeNs: BigInt(identity.ctimeNs),
|
|
299
|
+
...(identity.sha256 ? { sha256: identity.sha256 } : {}),
|
|
298
300
|
};
|
|
299
301
|
}
|
|
300
302
|
|
|
@@ -1704,6 +1706,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1704
1706
|
return {
|
|
1705
1707
|
dev: identity.dev.toString(),
|
|
1706
1708
|
ino: identity.ino.toString(),
|
|
1709
|
+
nlink: identity.nlink.toString(),
|
|
1707
1710
|
size: identity.size.toString(),
|
|
1708
1711
|
mtimeNs: identity.mtimeNs.toString(),
|
|
1709
1712
|
ctimeNs: identity.ctimeNs.toString(),
|
|
@@ -1718,17 +1721,20 @@ export class ManagedSessionDescendantStore {
|
|
|
1718
1721
|
): ManagedAppendReceipt {
|
|
1719
1722
|
this.#beforeMutation();
|
|
1720
1723
|
this.#assertBound();
|
|
1724
|
+
let appendExpectation = expected;
|
|
1721
1725
|
if (!this.#authority) {
|
|
1722
1726
|
const current = captureManagedFileIdentityStreamingNoFollow(this.#resolve(relativePath));
|
|
1723
|
-
|
|
1724
|
-
current.dev.toString()
|
|
1725
|
-
current.ino.toString()
|
|
1726
|
-
current.
|
|
1727
|
-
current.
|
|
1728
|
-
current.
|
|
1729
|
-
|
|
1730
|
-
)
|
|
1727
|
+
const identityMatches =
|
|
1728
|
+
current.dev.toString() === expected.dev &&
|
|
1729
|
+
current.ino.toString() === expected.ino &&
|
|
1730
|
+
current.nlink.toString() === expected.nlink &&
|
|
1731
|
+
current.size.toString() === expected.size &&
|
|
1732
|
+
current.sha256 === expected.sha256;
|
|
1733
|
+
if (!identityMatches) {
|
|
1731
1734
|
throw new Error("managed_append_identity_mismatch");
|
|
1735
|
+
}
|
|
1736
|
+
// mtime/ctime drift on the same file object is harmless. The append
|
|
1737
|
+
// helper performs its own descriptor-bound mutation check.
|
|
1732
1738
|
const appended = appendManagedFileStreamingSync(
|
|
1733
1739
|
this.#resolve(relativePath),
|
|
1734
1740
|
bytes,
|
|
@@ -1738,16 +1744,35 @@ export class ManagedSessionDescendantStore {
|
|
|
1738
1744
|
this.#assertBound();
|
|
1739
1745
|
return managedAppendReceiptFromIdentity(appended);
|
|
1740
1746
|
}
|
|
1747
|
+
const current = this.captureBoundedAppendExpectation(relativePath);
|
|
1748
|
+
if (!current) throw new Error("managed_append_identity_mismatch");
|
|
1749
|
+
if (
|
|
1750
|
+
current.dev !== expected.dev ||
|
|
1751
|
+
current.ino !== expected.ino ||
|
|
1752
|
+
current.nlink !== expected.nlink ||
|
|
1753
|
+
current.size !== expected.size ||
|
|
1754
|
+
current.sha256 !== expected.sha256
|
|
1755
|
+
)
|
|
1756
|
+
throw new Error("managed_append_identity_mismatch");
|
|
1757
|
+
const descriptorMatches =
|
|
1758
|
+
current.dev === expected.dev &&
|
|
1759
|
+
current.ino === expected.ino &&
|
|
1760
|
+
current.nlink === expected.nlink &&
|
|
1761
|
+
current.size === expected.size &&
|
|
1762
|
+
current.mtimeNs === expected.mtimeNs &&
|
|
1763
|
+
current.ctimeNs === expected.ctimeNs &&
|
|
1764
|
+
current.sha256 === expected.sha256;
|
|
1765
|
+
if (!descriptorMatches) appendExpectation = current;
|
|
1741
1766
|
const relative = this.#relative(this.#resolve(relativePath));
|
|
1742
1767
|
const appended = this.#authority.appendManaged(
|
|
1743
1768
|
relative,
|
|
1744
1769
|
bytes,
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1770
|
+
appendExpectation.dev,
|
|
1771
|
+
appendExpectation.ino,
|
|
1772
|
+
appendExpectation.size,
|
|
1773
|
+
appendExpectation.mtimeNs,
|
|
1774
|
+
appendExpectation.ctimeNs,
|
|
1775
|
+
appendExpectation.sha256,
|
|
1751
1776
|
);
|
|
1752
1777
|
if (!appended.ok) throw managedAppendFailure(appended.code);
|
|
1753
1778
|
if (!appended.identity)
|
|
@@ -1762,15 +1787,28 @@ export class ManagedSessionDescendantStore {
|
|
|
1762
1787
|
expected: ManagedFileIdentity,
|
|
1763
1788
|
): ManagedAppendReceipt {
|
|
1764
1789
|
const bounded = this.captureBoundedAppendExpectation(relativePath);
|
|
1765
|
-
if (
|
|
1766
|
-
|
|
1767
|
-
bounded.dev
|
|
1768
|
-
bounded.ino
|
|
1769
|
-
bounded.
|
|
1770
|
-
bounded.
|
|
1771
|
-
bounded.
|
|
1772
|
-
|
|
1773
|
-
|
|
1790
|
+
if (!bounded) throw new Error("managed_append_identity_mismatch");
|
|
1791
|
+
const descriptorMatches =
|
|
1792
|
+
bounded.dev === expected.dev.toString() &&
|
|
1793
|
+
bounded.ino === expected.ino.toString() &&
|
|
1794
|
+
bounded.nlink === expected.nlink.toString() &&
|
|
1795
|
+
bounded.size === String(expected.size) &&
|
|
1796
|
+
bounded.mtimeNs === expected.mtimeNs.toString() &&
|
|
1797
|
+
bounded.ctimeNs === expected.ctimeNs.toString();
|
|
1798
|
+
if (!descriptorMatches) {
|
|
1799
|
+
// Metadata-only drift is safe only when the same unlinked file object and
|
|
1800
|
+
// byte-exact predecessor are independently proven. Never accept a changed
|
|
1801
|
+
// object, length, link count, or digest as a benign touch.
|
|
1802
|
+
if (
|
|
1803
|
+
!expected.sha256 ||
|
|
1804
|
+
bounded.dev !== expected.dev.toString() ||
|
|
1805
|
+
bounded.ino !== expected.ino.toString() ||
|
|
1806
|
+
bounded.nlink !== expected.nlink.toString() ||
|
|
1807
|
+
bounded.size !== String(expected.size) ||
|
|
1808
|
+
bounded.sha256 !== expected.sha256
|
|
1809
|
+
)
|
|
1810
|
+
throw new Error("managed_append_identity_mismatch");
|
|
1811
|
+
}
|
|
1774
1812
|
return this.appendExpectedSync(relativePath, bytes, bounded);
|
|
1775
1813
|
}
|
|
1776
1814
|
|
|
@@ -8385,7 +8385,7 @@ export class SessionManager {
|
|
|
8385
8385
|
this.#usageStatistics = commit.usageStatistics;
|
|
8386
8386
|
this.#flushed = true;
|
|
8387
8387
|
this.#ensuredOnDisk = true;
|
|
8388
|
-
this.#managedPersistExpectedIdentity =
|
|
8388
|
+
this.#managedPersistExpectedIdentity = this.#captureManagedPersistIdentity(sessionFile);
|
|
8389
8389
|
this.#lazyReopenSucceeded = true;
|
|
8390
8390
|
this.#lazyReopenFallbackReason = undefined;
|
|
8391
8391
|
initialized = true;
|
|
@@ -8572,7 +8572,7 @@ export class SessionManager {
|
|
|
8572
8572
|
this.#lazyReopenFallbackReason = "bounded_first_open_descriptor_changed";
|
|
8573
8573
|
return false;
|
|
8574
8574
|
}
|
|
8575
|
-
this.#managedPersistExpectedIdentity =
|
|
8575
|
+
this.#managedPersistExpectedIdentity = this.#captureManagedPersistIdentity(sessionFile);
|
|
8576
8576
|
this.#sessionId = discovery.header.id;
|
|
8577
8577
|
this.#sessionName = discovery.header.title;
|
|
8578
8578
|
this.#titleSource = discovery.header.titleSource;
|
|
@@ -14945,9 +14945,27 @@ export class SessionManager {
|
|
|
14945
14945
|
this.#managedPersistExpectedIdentity = undefined;
|
|
14946
14946
|
return;
|
|
14947
14947
|
}
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14948
|
+
this.#managedPersistExpectedIdentity = this.#captureManagedPersistIdentity(sessionFile);
|
|
14949
|
+
}
|
|
14950
|
+
|
|
14951
|
+
/** Capture one descriptor-bound digest for a future metadata-drift comparison. */
|
|
14952
|
+
#captureManagedPersistIdentity(sessionFile: string): ManagedFileIdentity {
|
|
14953
|
+
const store = this.#managedTranscriptStore(sessionFile);
|
|
14954
|
+
const relativePath = path.basename(sessionFile);
|
|
14955
|
+
const bounded = store.captureBoundedAppendExpectation(relativePath);
|
|
14956
|
+
const descriptor = store.descriptorExpected(relativePath);
|
|
14957
|
+
if (!bounded || !descriptor) throw new Error("managed_persist_identity_unavailable");
|
|
14958
|
+
const identity = managedIdentityFromDescriptor(descriptor);
|
|
14959
|
+
if (
|
|
14960
|
+
bounded.dev !== identity.dev.toString() ||
|
|
14961
|
+
bounded.ino !== identity.ino.toString() ||
|
|
14962
|
+
bounded.nlink !== identity.nlink.toString() ||
|
|
14963
|
+
bounded.size !== String(identity.size) ||
|
|
14964
|
+
bounded.mtimeNs !== identity.mtimeNs.toString() ||
|
|
14965
|
+
bounded.ctimeNs !== identity.ctimeNs.toString()
|
|
14966
|
+
)
|
|
14967
|
+
throw new Error("managed_persist_identity_unavailable");
|
|
14968
|
+
return { ...identity, sha256: bounded.sha256 };
|
|
14951
14969
|
}
|
|
14952
14970
|
/** Current transcript descriptor, or null when unavailable. */
|
|
14953
14971
|
#managedDescriptorSnapshotOrNull(): DescriptorSnapshot | null {
|
|
@@ -14997,7 +15015,7 @@ export class SessionManager {
|
|
|
14997
15015
|
} else store.replaceSync(relativePath, bytes);
|
|
14998
15016
|
const descriptor = store.descriptorExpected(relativePath);
|
|
14999
15017
|
if (!descriptor) throw new Error("managed_replace_identity_unavailable");
|
|
15000
|
-
this.#managedPersistExpectedIdentity =
|
|
15018
|
+
this.#managedPersistExpectedIdentity = this.#captureManagedPersistIdentity(sessionFile);
|
|
15001
15019
|
this.#publishCommitMarkerFromCurrentTranscriptSync();
|
|
15002
15020
|
return;
|
|
15003
15021
|
}
|
|
@@ -19961,7 +19979,7 @@ export class SessionManager {
|
|
|
19961
19979
|
const returnManagedDescriptor = managedInspectionStore?.descriptorExpected(path.basename(filePath));
|
|
19962
19980
|
if (!returnManagedDescriptor || !sameDescriptor(managedBoundedDescriptor, returnManagedDescriptor))
|
|
19963
19981
|
throw new Error("Could not open session: unstable");
|
|
19964
|
-
manager.#managedPersistExpectedIdentity =
|
|
19982
|
+
manager.#managedPersistExpectedIdentity = manager.#captureManagedPersistIdentity(filePath);
|
|
19965
19983
|
const header = manager.#fileEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
19966
19984
|
if (header?.cwd) manager.cwd = header.cwd;
|
|
19967
19985
|
manager.buildSessionContext();
|
|
@@ -20079,7 +20097,7 @@ export class SessionManager {
|
|
|
20079
20097
|
const finalDescriptor = store.descriptorExpected(path.basename(resolved));
|
|
20080
20098
|
if (!finalDescriptor || !sameDescriptor(capturedDescriptor, finalDescriptor))
|
|
20081
20099
|
throw new Error("source_changed");
|
|
20082
|
-
boundedManager.#managedPersistExpectedIdentity =
|
|
20100
|
+
boundedManager.#managedPersistExpectedIdentity = boundedManager.#captureManagedPersistIdentity(resolved);
|
|
20083
20101
|
boundedManager.buildSessionContext();
|
|
20084
20102
|
return boundedManager;
|
|
20085
20103
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
} from "./output-meta";
|
|
60
60
|
import { resolveToCwd } from "./path-utils";
|
|
61
61
|
import { formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
62
|
+
import { checkTmuxSelfInjection } from "./tmux-self-injection-guard";
|
|
62
63
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
63
64
|
import { toolResult } from "./tool-result";
|
|
64
65
|
import { clampTimeout, TOOL_TIMEOUTS } from "./tool-timeouts";
|
|
@@ -1053,6 +1054,19 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
1053
1054
|
...internalUrlOptions,
|
|
1054
1055
|
ensureLocalParentDirs: this.session.bashRestrictionProfile !== "read-only",
|
|
1055
1056
|
});
|
|
1057
|
+
// Issue #5039: refuse tmux write verbs whose target resolves to this
|
|
1058
|
+
// agent's own tmux pane — such bytes forge a human `user` turn in the
|
|
1059
|
+
// transcript. This is after command expansion but before any spawn, so
|
|
1060
|
+
// blocked attempts inject no bytes and create no transcript turn.
|
|
1061
|
+
if (this.session.bashRestrictionProfile !== "read-only") {
|
|
1062
|
+
const injection = await checkTmuxSelfInjection(command, {
|
|
1063
|
+
env: process.env,
|
|
1064
|
+
cwd: this.session.cwd,
|
|
1065
|
+
});
|
|
1066
|
+
if (injection.block) {
|
|
1067
|
+
throw new ToolError(injection.reason ?? "Blocked: tmux self-pane injection.");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1056
1070
|
const expandedEnv = env
|
|
1057
1071
|
? Object.fromEntries(
|
|
1058
1072
|
await Promise.all(
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -11,17 +11,20 @@ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
|
11
11
|
const FILE_LINE_RANGE_RE = /^(?:L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*|raw|conflicts)$/i;
|
|
12
12
|
const FILE_LINE_RANGE_ONLY_RE = /^L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*$/i;
|
|
13
13
|
const FILE_RAW_ONLY_RE = /^raw$/i;
|
|
14
|
-
// Schemes whose authority grammar is identifier-shaped and
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
const
|
|
14
|
+
// Schemes whose authority grammar is identifier-shaped and cannot contain a
|
|
15
|
+
// literal colon. Colons in a path, query, or fragment remain part of the URL
|
|
16
|
+
// and are never considered selectors.
|
|
17
|
+
const INTERNAL_SCHEMES_WITH_UNAMBIGUOUS_AUTHORITIES: Record<string, true> = {
|
|
18
18
|
agent: true,
|
|
19
19
|
artifact: true,
|
|
20
|
-
issue: true,
|
|
21
|
-
local: true,
|
|
22
20
|
memory: true,
|
|
23
|
-
|
|
21
|
+
issue: true,
|
|
24
22
|
pr: true,
|
|
23
|
+
};
|
|
24
|
+
const INTERNAL_SCHEMES_WITH_SELECTORS: Record<string, true> = {
|
|
25
|
+
...INTERNAL_SCHEMES_WITH_UNAMBIGUOUS_AUTHORITIES,
|
|
26
|
+
gjc: true,
|
|
27
|
+
local: true,
|
|
25
28
|
rule: true,
|
|
26
29
|
skill: true,
|
|
27
30
|
};
|
|
@@ -33,6 +36,18 @@ function normalizeUnicodeSpaces(str: string): string {
|
|
|
33
36
|
return str.replace(UNICODE_SPACES, " ");
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
function rawSkillPrefixForName(authority: string, skillName: string): string | undefined {
|
|
40
|
+
for (let index = 1; index < authority.length; index++) {
|
|
41
|
+
if (authority[index] !== ":") continue;
|
|
42
|
+
try {
|
|
43
|
+
if (decodeURIComponent(authority.slice(0, index)) === skillName) return authority.slice(0, index);
|
|
44
|
+
} catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
function tryMacOSScreenshotPath(filePath: string): string {
|
|
37
52
|
// macOS writes a narrow no-break space before AM/PM, but the model normalizes it
|
|
38
53
|
// to a plain space. The original name is not always `…PM.png`: attachment paths
|
|
@@ -149,12 +164,11 @@ export function splitPathAndSel(rawPath: string): { path: string; sel?: string }
|
|
|
149
164
|
/**
|
|
150
165
|
* Variant of {@link splitPathAndSel} for internal URLs (`scheme://...`).
|
|
151
166
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* skill name from its selector without making path-utils depend on skills.
|
|
167
|
+
* Selector-capable internal authorities use identifier-shaped resource names,
|
|
168
|
+
* so an authority-only `:<tail>` is an explicit read selector even when the
|
|
169
|
+
* selector is malformed. Skill authorities are the exception: namespaced
|
|
170
|
+
* skill names already contain a colon, so the active registry gets first
|
|
171
|
+
* refusal and the fallback recognizes a later colon as the selector boundary.
|
|
158
172
|
*/
|
|
159
173
|
export function splitInternalUrlSel(
|
|
160
174
|
rawPath: string,
|
|
@@ -167,32 +181,66 @@ export function splitInternalUrlSel(
|
|
|
167
181
|
|
|
168
182
|
const schemeEnd = schemeMatch[0].length;
|
|
169
183
|
const authorityTerminator = rawPath.slice(schemeEnd).search(/[/?#]/);
|
|
170
|
-
|
|
171
|
-
const authority = rawPath.slice(schemeEnd);
|
|
184
|
+
const authorityEnd = authorityTerminator === -1 ? rawPath.length : schemeEnd + authorityTerminator;
|
|
185
|
+
const authority = rawPath.slice(schemeEnd, authorityEnd);
|
|
186
|
+
const authoritySuffix = rawPath.slice(authorityEnd);
|
|
172
187
|
const firstColon = authority.indexOf(":");
|
|
173
188
|
if (firstColon === -1) return { path: rawPath };
|
|
174
|
-
|
|
175
|
-
if (scheme === "artifact") {
|
|
176
|
-
return { path: rawPath.slice(0, schemeEnd + firstColon), sel: authority.slice(firstColon + 1) };
|
|
177
|
-
}
|
|
189
|
+
if (firstColon === 0) return { path: rawPath };
|
|
178
190
|
|
|
179
191
|
if (scheme === "skill") {
|
|
180
192
|
const activeSkillNames = options.activeSkillNames ?? [];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
let decodedAuthority: string | undefined;
|
|
194
|
+
try {
|
|
195
|
+
decodedAuthority = decodeURIComponent(authority);
|
|
196
|
+
} catch {
|
|
197
|
+
// Let the resolver report malformed URL encoding when no selector boundary
|
|
198
|
+
// can be established from the raw authority.
|
|
199
|
+
}
|
|
200
|
+
if (decodedAuthority !== undefined && activeSkillNames.includes(decodedAuthority)) return { path: rawPath };
|
|
201
|
+
const rawSkillPrefix = activeSkillNames
|
|
202
|
+
.map(name => ({ name, prefix: rawSkillPrefixForName(authority, name) }))
|
|
203
|
+
.filter(item => item.prefix !== undefined)
|
|
204
|
+
.sort((a, b) => b.name.length - a.name.length)[0]?.prefix;
|
|
205
|
+
if (rawSkillPrefix) {
|
|
186
206
|
return {
|
|
187
|
-
path: `${rawPath.slice(0, schemeEnd)}${
|
|
188
|
-
sel: authority.slice(
|
|
207
|
+
path: `${rawPath.slice(0, schemeEnd)}${rawSkillPrefix}${authoritySuffix}`,
|
|
208
|
+
sel: authority.slice(rawSkillPrefix.length + 1),
|
|
189
209
|
};
|
|
190
210
|
}
|
|
211
|
+
const strict = splitPathAndSel(authority);
|
|
212
|
+
if (strict.sel !== undefined) {
|
|
213
|
+
return { path: `${rawPath.slice(0, schemeEnd)}${strict.path}${authoritySuffix}`, sel: strict.sel };
|
|
214
|
+
}
|
|
215
|
+
const firstTail = authority.slice(firstColon + 1);
|
|
216
|
+
if (FILE_LINE_RANGE_RE.test(firstTail)) {
|
|
217
|
+
return { path: `${rawPath.slice(0, schemeEnd + firstColon)}${authoritySuffix}`, sel: firstTail };
|
|
218
|
+
}
|
|
219
|
+
const selectorColon = authority.indexOf(":", firstColon + 1);
|
|
220
|
+
if (selectorColon === -1) return { path: rawPath };
|
|
221
|
+
return {
|
|
222
|
+
path: `${rawPath.slice(0, schemeEnd + selectorColon)}${authoritySuffix}`,
|
|
223
|
+
sel: authority.slice(selectorColon + 1),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!INTERNAL_SCHEMES_WITH_UNAMBIGUOUS_AUTHORITIES[scheme]) {
|
|
228
|
+
// A path/query/fragment after a path-like authority makes a colon-bearing
|
|
229
|
+
// resource identity indistinguishable from an authority selector. Preserve
|
|
230
|
+
// the complete URL; path-like selectors are accepted only on authority-only
|
|
231
|
+
// URLs, where the existing strict grammar is the sole interpretation.
|
|
232
|
+
if (authoritySuffix !== "") return { path: rawPath };
|
|
233
|
+
const strict = splitPathAndSel(authority);
|
|
234
|
+
if (strict.sel !== undefined) {
|
|
235
|
+
return { path: `${rawPath.slice(0, schemeEnd)}${strict.path}`, sel: strict.sel };
|
|
236
|
+
}
|
|
237
|
+
return { path: rawPath };
|
|
191
238
|
}
|
|
192
239
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
240
|
+
return {
|
|
241
|
+
path: `${rawPath.slice(0, schemeEnd + firstColon)}${authoritySuffix}`,
|
|
242
|
+
sel: authority.slice(firstColon + 1),
|
|
243
|
+
};
|
|
196
244
|
}
|
|
197
245
|
|
|
198
246
|
function assertNotInternalUrl(expanded: string, original: string): void {
|