@lanes-sh/link 0.7.2 → 0.9.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/README.md +20 -10
- package/instructions/agents/lanes-link-scout.md +2 -2
- package/instructions/skills/lanes-link/SKILL.md +148 -73
- package/package.json +2 -1
- package/src/audit/index.ts +8 -1
- package/src/auth/index.ts +58 -2
- package/src/auth/lanes/assertion.ts +256 -0
- package/src/auth/lanes/callback.ts +135 -0
- package/src/auth/lanes/federation.ts +50 -0
- package/src/auth/lanes/login.ts +294 -0
- package/src/auth/lanes/members.ts +103 -0
- package/src/auth/lanes/session.ts +97 -0
- package/src/auth/oauth/grant.ts +183 -0
- package/src/auth/oauth/result.ts +27 -0
- package/src/auth/oauth/server.ts +176 -203
- package/src/auth/oauth/store.ts +85 -4
- package/src/auth/remote.ts +32 -9
- package/src/cli/accepts.ts +109 -0
- package/src/cli/argv.ts +57 -3
- package/src/cli/audit-change.ts +140 -0
- package/src/cli/callback-page.ts +36 -115
- package/src/cli/commands/auth-dispatch.ts +48 -0
- package/src/cli/commands/auth.ts +229 -0
- package/src/cli/commands/connect/accounts.ts +4 -4
- package/src/cli/commands/connect/authorise.ts +4 -4
- package/src/cli/commands/connect/bind-credential.ts +2 -1
- package/src/cli/commands/connect/custom/index.ts +1 -1
- package/src/cli/commands/connect/custom/write.ts +2 -2
- package/src/cli/commands/connect/grant.ts +29 -14
- package/src/cli/commands/connect/index.ts +90 -88
- package/src/cli/commands/connect/options.ts +83 -0
- package/src/cli/commands/connect/registration.ts +50 -0
- package/src/cli/commands/connect/requirements.ts +1 -1
- package/src/cli/commands/connect/settle.ts +16 -6
- package/src/cli/commands/connect/target-note.ts +7 -2
- package/src/cli/commands/connect/unknown.ts +1 -1
- package/src/cli/commands/connect/variables.ts +3 -2
- package/src/cli/commands/connection-list.ts +116 -0
- package/src/cli/commands/connection.ts +183 -165
- package/src/cli/commands/grant.ts +140 -0
- package/src/cli/commands/identity.ts +24 -12
- package/src/cli/commands/knowledge/index.ts +49 -89
- package/src/cli/commands/knowledge/migrate.ts +79 -13
- package/src/cli/commands/knowledge/show.ts +97 -0
- package/src/cli/commands/knowledge.ts +2 -1
- package/src/cli/commands/mcp/harnesses.ts +30 -8
- package/src/cli/commands/mcp/onboarding.ts +86 -0
- package/src/cli/commands/mcp/register.ts +16 -2
- package/src/cli/commands/mcp.ts +1 -0
- package/src/cli/commands/members.ts +288 -0
- package/src/cli/commands/operate/attach.ts +3 -3
- package/src/cli/commands/operate/audit.ts +11 -7
- package/src/cli/commands/operate/auth.ts +28 -11
- package/src/cli/commands/operate/findings.ts +2 -1
- package/src/cli/commands/operate/inspect.ts +37 -19
- package/src/cli/commands/operate/migrate.ts +33 -13
- package/src/cli/commands/operate/outputs.ts +3 -3
- package/src/cli/commands/operate/pair-certificate.ts +141 -0
- package/src/cli/commands/operate/pair.ts +324 -0
- package/src/cli/commands/operate/policy.ts +73 -22
- package/src/cli/commands/operate/serve.ts +53 -5
- package/src/cli/commands/operate/status.ts +18 -10
- package/src/cli/commands/operate/tools.ts +2 -2
- package/src/cli/commands/operate.ts +2 -0
- package/src/cli/commands/owner/assets.ts +2 -2
- package/src/cli/commands/owner/entities.ts +2 -2
- package/src/cli/commands/owner/memory.ts +2 -2
- package/src/cli/commands/owner/shared.ts +13 -2
- package/src/cli/commands/owner/skills.ts +28 -8
- package/src/cli/commands/owner/tasks.ts +2 -2
- package/src/cli/commands/owner/vault.ts +3 -3
- package/src/cli/commands/profile/disposition.ts +236 -0
- package/src/cli/commands/profile/removal.ts +154 -64
- package/src/cli/commands/profile/remove.ts +83 -7
- package/src/cli/commands/profile.ts +79 -16
- package/src/cli/commands/relabel.ts +112 -0
- package/src/cli/commands/secrets.ts +39 -17
- package/src/cli/commands/set-workspace.ts +96 -0
- package/src/cli/commands/setup.ts +2 -2
- package/src/cli/commands/sync.ts +8 -8
- package/src/cli/commands/target.ts +9 -7
- package/src/cli/commands/update-migration.ts +54 -0
- package/src/cli/commands/update.ts +78 -24
- package/src/cli/config-edit.ts +99 -143
- package/src/cli/config-migrate.ts +82 -64
- package/src/cli/config-repair-sweep.ts +119 -0
- package/src/cli/config-repair.ts +131 -125
- package/src/cli/config-templates.ts +200 -0
- package/src/cli/contract3-credentials.ts +294 -0
- package/src/cli/contract3-data.ts +262 -0
- package/src/cli/contract3-layout.ts +46 -0
- package/src/cli/contract3-shape.ts +212 -0
- package/src/cli/contract3.ts +399 -0
- package/src/cli/contract4-credentials.ts +207 -0
- package/src/cli/contract4-data.ts +399 -0
- package/src/cli/contract4-rename.ts +73 -0
- package/src/cli/contract4-yaml.ts +223 -0
- package/src/cli/contract4.ts +342 -0
- package/src/cli/endpoint-url.ts +1 -1
- package/src/cli/identity.ts +44 -26
- package/src/cli/lanes.ts +25 -1
- package/src/cli/main.ts +94 -14
- package/src/cli/migrate-move.ts +166 -0
- package/src/cli/migrate-plan.ts +12 -6
- package/src/cli/output.ts +34 -1
- package/src/cli/publish.ts +6 -7
- package/src/cli/runtime/open.ts +64 -99
- package/src/cli/runtime/registry.ts +6 -7
- package/src/cli/runtime/select.ts +2 -11
- package/src/cli/runtime/stores.ts +58 -0
- package/src/cli/runtime/types.ts +106 -0
- package/src/cli/runtime/vault.ts +19 -4
- package/src/cli/runtime/workspace.ts +60 -0
- package/src/cli/runtime.ts +2 -1
- package/src/cli/selection-require.ts +44 -13
- package/src/cli/selection.ts +127 -145
- package/src/cli/usage.ts +40 -20
- package/src/cli/workspace-migrate.ts +152 -22
- package/src/connectivity/manifest/provider.ts +34 -13
- package/src/connectivity/manifest/requirements.ts +1 -1
- package/src/connectivity/transports/imap/parser.ts +70 -9
- package/src/deployments/adapters/filesystem.ts +18 -3
- package/src/deployments/bind.ts +1 -1
- package/src/deployments/deploy.ts +38 -29
- package/src/deployments/gcp/bucket.ts +58 -11
- package/src/deployments/gcp/provision.ts +7 -7
- package/src/deployments/knowledge.ts +9 -4
- package/src/deployments/prepare.ts +72 -24
- package/src/deployments/record.ts +1 -1
- package/src/deployments/report.ts +2 -2
- package/src/deployments/serving.ts +15 -74
- package/src/deployments/target.ts +34 -13
- package/src/deployments/upload.ts +60 -27
- package/src/dispatch/deps.ts +88 -0
- package/src/dispatch/dispatch.ts +21 -62
- package/src/policy/index.ts +47 -15
- package/src/profile/connections.ts +195 -0
- package/src/profile/deployments.ts +86 -8
- package/src/profile/index.ts +35 -6
- package/src/profile/knowledge.ts +18 -5
- package/src/profile/layout.ts +163 -90
- package/src/profile/load.ts +133 -64
- package/src/profile/pairing.ts +32 -0
- package/src/profile/primitives.ts +35 -1
- package/src/profile/registry.ts +6 -6
- package/src/profile/schema.ts +181 -21
- package/src/profile/targets.ts +21 -9
- package/src/profile/testing.ts +104 -2
- package/src/profile/workspace.ts +124 -33
- package/src/providers/assets/provider.ts +6 -6
- package/src/providers/custom/index.ts +1 -1
- package/src/providers/custom/load.ts +2 -3
- package/src/providers/entities/provider.ts +6 -6
- package/src/providers/entities/writes.ts +1 -1
- package/src/providers/identity/provider.ts +2 -2
- package/src/providers/memory/provider.ts +26 -8
- package/src/providers/setup/plan.ts +1 -1
- package/src/providers/setup/provider.ts +3 -3
- package/src/providers/skills/provider.ts +2 -2
- package/src/providers/slack/index.ts +2 -2
- package/src/providers/tasks/provider.ts +6 -6
- package/src/providers/vault/provider.ts +1 -1
- package/src/registry/policy-bridge.ts +33 -11
- package/src/registry/reconcile.ts +4 -4
- package/src/registry/registry.ts +1 -1
- package/src/server/authorization.ts +94 -0
- package/src/server/edge.ts +14 -1
- package/src/server/endpoint.ts +89 -104
- package/src/server/generation.ts +10 -1
- package/src/server/harness.ts +71 -13
- package/src/server/index.ts +31 -0
- package/src/server/mcp/build.ts +20 -1
- package/src/server/mcp/client-info.ts +54 -0
- package/src/server/mcp/guide.ts +120 -0
- package/src/server/mcp/instructions.ts +22 -22
- package/src/server/mcp/prompts.ts +7 -3
- package/src/server/mcp/resources.ts +16 -8
- package/src/server/mcp/routing.ts +3 -3
- package/src/server/mcp/tools.ts +25 -6
- package/src/server/mcp/visibility.ts +74 -7
- package/src/server/oauth.ts +29 -109
- package/src/server/read/credential.ts +134 -0
- package/src/server/read/deployed.ts +56 -0
- package/src/server/read/listener.ts +54 -0
- package/src/server/read/open.ts +101 -0
- package/src/server/read/routes.ts +247 -0
- package/src/server/read/state.ts +171 -0
- package/src/stores/blobs/conformance.ts +19 -0
- package/src/stores/state/index.ts +76 -10
- package/src/stores/state/testing.ts +5 -1
package/src/profile/workspace.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
+
import { layout, legacyProfileConfig, LEGACY_WORKSPACE_FILE, PROFILE_FILE } from './layout.ts';
|
|
2
3
|
import { isRemoteWorkspace, readWorkspaceFile, workspaceFiles } from './files.ts';
|
|
3
4
|
import { parseConfig, type LoadedConfig } from './load.ts';
|
|
4
5
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
6
|
import { homedir } from 'node:os';
|
|
6
7
|
import { parse as parseYaml } from 'yaml';
|
|
7
8
|
import { ConfigError } from './load.ts';
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
SUPPORTED_CONTRACT,
|
|
11
|
+
connectionsFileSchema,
|
|
12
|
+
workspaceSchema,
|
|
13
|
+
type Config,
|
|
14
|
+
type ConnectionsFile,
|
|
15
|
+
type WorkspaceConfig,
|
|
16
|
+
} from './schema.ts';
|
|
17
|
+
import { assertConnectionsUnique, assertNoRenamedProviders } from './connections.ts';
|
|
18
|
+
import { findSecrets, formatSecretFindings } from './secret-detection.ts';
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* Workspace and profile resolution.
|
|
12
22
|
*
|
|
13
|
-
* A workspace
|
|
14
|
-
*
|
|
15
|
-
* lanes-link.yaml workspace settings: contract, default_profile
|
|
16
|
-
* profiles/
|
|
17
|
-
* personal.yaml
|
|
18
|
-
* work.yaml
|
|
19
|
-
* data/ local state per profile, gitignored
|
|
23
|
+
* A workspace holds its registry, its accounts, and one directory per profile.
|
|
24
|
+
* `layout.ts` has the tree.
|
|
20
25
|
*
|
|
21
26
|
* **A command says which profile it means, or it does not run** (ADR-037).
|
|
22
27
|
* `--profile` is the only thing that selects one. `LANES_LINK_PROFILE` and
|
|
@@ -24,23 +29,23 @@ import { workspaceSchema, type Config, type WorkspaceConfig } from './schema.ts'
|
|
|
24
29
|
*
|
|
25
30
|
* The argument this replaces was that persisted selection is how operators act
|
|
26
31
|
* on the wrong thing, and that a *visible* fallback — an exported variable, a
|
|
27
|
-
* key in a file the operator reads,
|
|
28
|
-
*
|
|
32
|
+
* key in a file the operator reads, a line printed before every command — was
|
|
33
|
+
* therefore safe. The first half stands and is why this rule exists at all.
|
|
29
34
|
* What did not survive is the conclusion: the printed line is a dim grey one,
|
|
30
35
|
* and a fallback made an ignored flag survivable, so `profile add --target
|
|
31
36
|
* cloud` dropping its flag surfaced on the *next* command, from a different
|
|
32
|
-
* source, detached from its cause. A resolver with nothing to fall back to
|
|
33
|
-
* cannot do that.
|
|
37
|
+
* source, detached from its cause. A resolver with nothing to fall back to cannot.
|
|
34
38
|
*
|
|
35
39
|
* The workspace root is deliberately not part of this and keeps its chain —
|
|
36
|
-
* `LANES_LINK_HOME`, then an ancestor holding `
|
|
37
|
-
*
|
|
40
|
+
* `LANES_LINK_HOME`, then an ancestor holding `workspaces.yaml` (or the
|
|
41
|
+
* `lanes-link.yaml` it was called before contract 4), then `~/.lanes-link`.
|
|
42
|
+
* Getting it wrong yields "no profiles here" rather than an
|
|
38
43
|
* action against the wrong account, it is the only channel a container has for
|
|
39
44
|
* its bucket (ADR-023), and the ancestor walk is what makes a per-repository
|
|
40
45
|
* workspace work at all.
|
|
41
46
|
*/
|
|
42
47
|
|
|
43
|
-
export const WORKSPACE_FILE = '
|
|
48
|
+
export const WORKSPACE_FILE = 'workspaces.yaml';
|
|
44
49
|
|
|
45
50
|
/** A profile, found. Everything a command needs before it has read the config. */
|
|
46
51
|
export interface ProfileSelection {
|
|
@@ -95,7 +100,9 @@ export function resolveWorkspaceRoot(options: ResolveOptions = {}): string {
|
|
|
95
100
|
// `existsSync`, not `Bun.file(path).size`: a missing file reports size 0,
|
|
96
101
|
// so a `>= 0` check would call every candidate a workspace and stop at the
|
|
97
102
|
// first directory it looked at.
|
|
98
|
-
|
|
103
|
+
// Either name: a root that cannot be found cannot be migrated.
|
|
104
|
+
const marker = (name: string): boolean => existsSync(join(directory, name));
|
|
105
|
+
if (marker(WORKSPACE_FILE) || marker(LEGACY_WORKSPACE_FILE)) return directory;
|
|
99
106
|
const parent = dirname(directory);
|
|
100
107
|
if (parent === directory) break;
|
|
101
108
|
directory = parent;
|
|
@@ -105,7 +112,7 @@ export function resolveWorkspaceRoot(options: ResolveOptions = {}): string {
|
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
/**
|
|
108
|
-
* Where Lanes Link itself is installed — the directory
|
|
115
|
+
* Where Lanes Link itself is installed — the directory with `package.json`,
|
|
109
116
|
* and with it `skills/` and `docs/`.
|
|
110
117
|
*
|
|
111
118
|
* Not the workspace: this is the code, not the operator's data. Found by
|
|
@@ -135,7 +142,7 @@ export function installRoot(from: string): string {
|
|
|
135
142
|
* workspace stopped being a directory.
|
|
136
143
|
*/
|
|
137
144
|
export function profilePath(workspaceRoot: string, profile: string): string {
|
|
138
|
-
const key =
|
|
145
|
+
const key = layout.profileConfig(profile);
|
|
139
146
|
return isRemoteWorkspace(workspaceRoot) ? `${workspaceRoot}/${key}` : join(workspaceRoot, key);
|
|
140
147
|
}
|
|
141
148
|
|
|
@@ -144,7 +151,7 @@ export async function loadProfileConfig(
|
|
|
144
151
|
workspaceRoot: string,
|
|
145
152
|
profile: string,
|
|
146
153
|
): Promise<LoadedConfig> {
|
|
147
|
-
const key =
|
|
154
|
+
const key = layout.profileConfig(profile);
|
|
148
155
|
const text = await readWorkspaceFile(workspaceFiles(workspaceRoot), key);
|
|
149
156
|
const shown = profilePath(workspaceRoot, profile);
|
|
150
157
|
|
|
@@ -152,10 +159,60 @@ export async function loadProfileConfig(
|
|
|
152
159
|
return parseConfig(text, shown);
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
/** `connections.yaml` sits beside `lanes-link.yaml`, at the workspace root. */
|
|
163
|
+
export const CONNECTIONS_FILE = 'connections.yaml';
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Every account authorised in this workspace (ADR-057).
|
|
167
|
+
*
|
|
168
|
+
* Absent is not an error. A workspace that has never connected anything has no
|
|
169
|
+
* file, and returning an empty set rather than throwing is what lets `profile
|
|
170
|
+
* add`, `status` and `doctor` answer on a fresh install — the same reasoning
|
|
171
|
+
* `readWorkspace` uses for returning null.
|
|
172
|
+
*
|
|
173
|
+
* Read through the workspace store rather than the filesystem, so a deployed
|
|
174
|
+
* revision whose root is a bucket URL loads it (ADR-049). That was the exact
|
|
175
|
+
* bug manifests hit: a filesystem read against a `gs://` root silently found
|
|
176
|
+
* nothing and every custom provider vanished.
|
|
177
|
+
*/
|
|
178
|
+
export async function readConnections(workspaceRoot: string): Promise<ConnectionsFile> {
|
|
179
|
+
const path = join(workspaceRoot, CONNECTIONS_FILE);
|
|
180
|
+
const text = await readWorkspaceFile(workspaceFiles(workspaceRoot), CONNECTIONS_FILE);
|
|
181
|
+
if (text === null) return { contract: SUPPORTED_CONTRACT, connections: [], oauth_apps: {} };
|
|
182
|
+
|
|
183
|
+
const raw = parseYaml(text);
|
|
184
|
+
|
|
185
|
+
const secrets = findSecrets(raw);
|
|
186
|
+
if (secrets.length > 0) {
|
|
187
|
+
throw new ConfigError(
|
|
188
|
+
`${path}: ${formatSecretFindings(secrets)}`,
|
|
189
|
+
secrets.map((finding) => finding.path),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const parsed = connectionsFileSchema.safeParse(raw);
|
|
194
|
+
if (!parsed.success) {
|
|
195
|
+
throw new ConfigError(
|
|
196
|
+
`${path}:\n${parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n')}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
assertConnectionsUnique(parsed.data.connections);
|
|
201
|
+
assertNoRenamedProviders(
|
|
202
|
+
parsed.data.connections,
|
|
203
|
+
'lanes link doctor --fix --profile <name> --workspace <name>',
|
|
204
|
+
);
|
|
205
|
+
return parsed.data;
|
|
206
|
+
}
|
|
207
|
+
|
|
155
208
|
export async function readWorkspace(workspaceRoot: string): Promise<WorkspaceConfig | null> {
|
|
156
|
-
|
|
157
|
-
|
|
209
|
+
// New name first: a workspace mid-migration still has the old file, and
|
|
210
|
+
// preferring it would undo the rename on the next write.
|
|
211
|
+
const files = workspaceFiles(workspaceRoot);
|
|
212
|
+
const current = await readWorkspaceFile(files, WORKSPACE_FILE);
|
|
213
|
+
const text = current ?? (await readWorkspaceFile(files, LEGACY_WORKSPACE_FILE));
|
|
158
214
|
if (text === null) return null;
|
|
215
|
+
const path = join(workspaceRoot, current === null ? LEGACY_WORKSPACE_FILE : WORKSPACE_FILE);
|
|
159
216
|
|
|
160
217
|
const parsed = workspaceSchema.safeParse(parseYaml(text));
|
|
161
218
|
if (!parsed.success) {
|
|
@@ -168,15 +225,34 @@ export async function readWorkspace(workspaceRoot: string): Promise<WorkspaceCon
|
|
|
168
225
|
|
|
169
226
|
export async function listProfiles(workspaceRoot: string): Promise<string[]> {
|
|
170
227
|
try {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
228
|
+
const root = `${layout.profilesRoot()}/`;
|
|
229
|
+
const entries = await workspaceFiles(workspaceRoot).list(root);
|
|
230
|
+
|
|
231
|
+
// A profile is a *directory* holding a `profile.yaml` (ADR-067), so the
|
|
232
|
+
// shape matched is `<name>/profile.yaml`. The listing walks each profile's
|
|
233
|
+
// memory and assets on the way past, which is the price of the declaration
|
|
234
|
+
// sitting beside the bytes; `audit.log/` is the workspace's, so the largest
|
|
235
|
+
// collection is not in here.
|
|
236
|
+
//
|
|
237
|
+
// **Both shapes**, because every migration enumerates through here: listing
|
|
238
|
+
// only the new one leaves an unmigrated workspace holding no profiles as
|
|
239
|
+
// far as its own migration is concerned. Same rule as the marker file.
|
|
240
|
+
const names = new Set<string>();
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
const rest = entry.key.slice(root.length);
|
|
243
|
+
const parts = rest.split('/');
|
|
244
|
+
|
|
245
|
+
if (parts.length === 2 && parts[1] === PROFILE_FILE) {
|
|
246
|
+
names.add(parts[0]!);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Contract 3 and earlier. `.example.yaml` was never a profile.
|
|
251
|
+
if (parts.length === 1 && rest.endsWith('.yaml') && !rest.endsWith('.example.yaml')) {
|
|
252
|
+
names.add(rest.slice(0, -'.yaml'.length));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return [...names].sort();
|
|
180
256
|
} catch {
|
|
181
257
|
return [];
|
|
182
258
|
}
|
|
@@ -199,8 +275,23 @@ export async function resolveSelection(options: ResolveOptions = {}): Promise<Pr
|
|
|
199
275
|
if (!profile) throw noProfileNamed(workspaceRoot, await listProfiles(workspaceRoot), env);
|
|
200
276
|
|
|
201
277
|
const path = profilePath(workspaceRoot, profile);
|
|
202
|
-
|
|
278
|
+
const files = workspaceFiles(workspaceRoot);
|
|
279
|
+
|
|
280
|
+
if (!(await files.has(layout.profileConfig(profile)))) {
|
|
203
281
|
const available = await listProfiles(workspaceRoot);
|
|
282
|
+
|
|
283
|
+
// **It exists, at the path contract 3 kept it.** Without this the refusal
|
|
284
|
+
// reads "profile personal does not exist. Available: personal" — what a
|
|
285
|
+
// listing that understands both layouts and a lookup that understands one
|
|
286
|
+
// produce together — and names no way forward (ADR-051).
|
|
287
|
+
if (await files.has(legacyProfileConfig(profile))) {
|
|
288
|
+
throw new ConfigError(
|
|
289
|
+
`Profile "${profile}" is still laid out the way contract 3 kept it, and nothing reads ` +
|
|
290
|
+
`that any more.\n Migrate it with: lanes link doctor --fix --profile ${profile} ` +
|
|
291
|
+
'--workspace <name>',
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
204
295
|
throw new ConfigError(
|
|
205
296
|
`Profile "${profile}" does not exist (looked for ${path}).\n` +
|
|
206
297
|
(available.length > 0 ? `Available: ${available.join(', ')}` : 'No profiles exist yet.'),
|
|
@@ -219,7 +310,7 @@ export function noProfileNamed(
|
|
|
219
310
|
if (available.length === 0) {
|
|
220
311
|
return new ConfigError(
|
|
221
312
|
`--profile is required, and ${workspaceRoot} holds no profiles yet.\n` +
|
|
222
|
-
' Create one with: lanes link profile add <name> --
|
|
313
|
+
' Create one with: lanes link profile add <name> --workspace local',
|
|
223
314
|
);
|
|
224
315
|
}
|
|
225
316
|
|
|
@@ -230,7 +321,7 @@ export function noProfileNamed(
|
|
|
230
321
|
'nothing else selects one.\n\n' +
|
|
231
322
|
` Profiles in ${workspaceRoot}\n` +
|
|
232
323
|
available.map((name) => ` ${name}`).join('\n') +
|
|
233
|
-
`\n\n e.g. lanes link status --profile ${available[0]} --
|
|
324
|
+
`\n\n e.g. lanes link status --profile ${available[0]} --workspace <name>` +
|
|
234
325
|
(stale
|
|
235
326
|
? `\n\n LANES_LINK_PROFILE=${stale} is set in this shell and is no longer read.\n` +
|
|
236
327
|
' Unset it, or pass --profile.'
|
|
@@ -72,7 +72,7 @@ const MAX_ASSET_BYTES = 25 * 1024 * 1024;
|
|
|
72
72
|
const MAX_TEXT_BYTES = 256 * 1024;
|
|
73
73
|
|
|
74
74
|
export const assetsProvider: ProviderDefinition = defineLocalProvider({
|
|
75
|
-
id: '
|
|
75
|
+
id: 'lanes_assets',
|
|
76
76
|
name: 'Assets',
|
|
77
77
|
version: '1.0.0',
|
|
78
78
|
description:
|
|
@@ -104,12 +104,12 @@ export const assetsProvider: ProviderDefinition = defineLocalProvider({
|
|
|
104
104
|
title: 'Stored file',
|
|
105
105
|
description:
|
|
106
106
|
'One stored file, addressed by its name. Text comes back as text; anything else is described rather than encoded.',
|
|
107
|
-
uriTemplate: 'assets://file/{name}',
|
|
107
|
+
uriTemplate: 'lanes-assets://file/{name}',
|
|
108
108
|
redact: keepKeys('uri'),
|
|
109
109
|
|
|
110
110
|
async list(context) {
|
|
111
111
|
return (await allAssets(context.storage)).map((asset) => ({
|
|
112
|
-
uri: `assets://file/${encodeURIComponent(asset.name)}`,
|
|
112
|
+
uri: `lanes-assets://file/${encodeURIComponent(asset.name)}`,
|
|
113
113
|
name: asset.name,
|
|
114
114
|
}));
|
|
115
115
|
},
|
|
@@ -171,7 +171,7 @@ export const assetsProvider: ProviderDefinition = defineLocalProvider({
|
|
|
171
171
|
...shown.flatMap((asset) => [
|
|
172
172
|
{
|
|
173
173
|
type: 'resource_link' as const,
|
|
174
|
-
uri: `assets://file/${encodeURIComponent(asset.name)}`,
|
|
174
|
+
uri: `lanes-assets://file/${encodeURIComponent(asset.name)}`,
|
|
175
175
|
name: asset.name,
|
|
176
176
|
},
|
|
177
177
|
{ type: 'text' as const, text: describeAsset(asset) },
|
|
@@ -194,7 +194,7 @@ export const assetsProvider: ProviderDefinition = defineLocalProvider({
|
|
|
194
194
|
name: 'get',
|
|
195
195
|
title: 'Read a stored file',
|
|
196
196
|
description:
|
|
197
|
-
'Return a text file\'s contents. A binary file is described instead — name, type, size, digest — because encoding it here is the cost this provider exists to avoid. The resource assets://file/{name} is the same content.',
|
|
197
|
+
'Return a text file\'s contents. A binary file is described instead — name, type, size, digest — because encoding it here is the cost this provider exists to avoid. The resource lanes-assets://file/{name} is the same content.',
|
|
198
198
|
inputSchema: z.object({ name: z.string().min(1).describe('The file name') }),
|
|
199
199
|
redact: keepKeys('name'),
|
|
200
200
|
async handler({ name }, context) {
|
|
@@ -271,7 +271,7 @@ export const assetsProvider: ProviderDefinition = defineLocalProvider({
|
|
|
271
271
|
},
|
|
272
272
|
{
|
|
273
273
|
type: 'resource_link',
|
|
274
|
-
uri: `assets://file/${encodeURIComponent(assetName)}`,
|
|
274
|
+
uri: `lanes-assets://file/${encodeURIComponent(assetName)}`,
|
|
275
275
|
name: assetName,
|
|
276
276
|
},
|
|
277
277
|
],
|
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
* connectivity type; `load.ts` reads and validates them.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
export {
|
|
15
|
+
export { loadWorkspaceProviders, parseManifest, type LoadedManifest } from './load.ts';
|
|
16
16
|
export { manifestTemplate } from './template.ts';
|
|
@@ -38,14 +38,13 @@ export interface LoadedManifest {
|
|
|
38
38
|
readonly path: string;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export async function
|
|
41
|
+
export async function loadWorkspaceProviders(
|
|
42
42
|
workspaceRoot: string,
|
|
43
|
-
profile: string,
|
|
44
43
|
/** Injected for tests. A bucket is the case this exists to cover. */
|
|
45
44
|
store?: BlobStore,
|
|
46
45
|
): Promise<LoadedManifest[]> {
|
|
47
46
|
const files = store ?? workspaceFiles(workspaceRoot);
|
|
48
|
-
const directory = layout.providers(
|
|
47
|
+
const directory = layout.providers();
|
|
49
48
|
|
|
50
49
|
let keys: string[];
|
|
51
50
|
try {
|
|
@@ -58,7 +58,7 @@ const DESCRIPTION =
|
|
|
58
58
|
'so more than one means ask rather than take the first.';
|
|
59
59
|
|
|
60
60
|
export const entitiesProvider: ProviderDefinition = defineLocalProvider({
|
|
61
|
-
id: '
|
|
61
|
+
id: 'lanes_entities',
|
|
62
62
|
name: 'Entities',
|
|
63
63
|
version: '1.0.0',
|
|
64
64
|
description: DESCRIPTION,
|
|
@@ -99,14 +99,14 @@ export const entitiesProvider: ProviderDefinition = defineLocalProvider({
|
|
|
99
99
|
name: 'entity',
|
|
100
100
|
title: 'Entity',
|
|
101
101
|
description: 'One declared entity, addressed by its id.',
|
|
102
|
-
uriTemplate: 'entities://entity/{id}',
|
|
102
|
+
uriTemplate: 'lanes-entities://entity/{id}',
|
|
103
103
|
mimeType: 'text/markdown',
|
|
104
104
|
redact: keepKeys('uri'),
|
|
105
105
|
|
|
106
106
|
async list(context) {
|
|
107
107
|
const catalogue = await openCatalogue(context.storage);
|
|
108
108
|
return catalogue.entities.map((entity) => ({
|
|
109
|
-
uri: `entities://entity/${encodeURIComponent(entity.id)}`,
|
|
109
|
+
uri: `lanes-entities://entity/${encodeURIComponent(entity.id)}`,
|
|
110
110
|
name: entity.name,
|
|
111
111
|
}));
|
|
112
112
|
},
|
|
@@ -230,7 +230,7 @@ export const entitiesProvider: ProviderDefinition = defineLocalProvider({
|
|
|
230
230
|
{ type: 'text', text: renderCandidates(matches, criteria, context.connection.key) },
|
|
231
231
|
...matches.candidates.map((one) => ({
|
|
232
232
|
type: 'resource_link' as const,
|
|
233
|
-
uri: `entities://entity/${encodeURIComponent(one.entity.id)}`,
|
|
233
|
+
uri: `lanes-entities://entity/${encodeURIComponent(one.entity.id)}`,
|
|
234
234
|
name: one.entity.name,
|
|
235
235
|
})),
|
|
236
236
|
],
|
|
@@ -244,7 +244,7 @@ export const entitiesProvider: ProviderDefinition = defineLocalProvider({
|
|
|
244
244
|
content: [
|
|
245
245
|
{
|
|
246
246
|
type: 'resource_link' as const,
|
|
247
|
-
uri: `entities://entity/${encodeURIComponent(found.id)}`,
|
|
247
|
+
uri: `lanes-entities://entity/${encodeURIComponent(found.id)}`,
|
|
248
248
|
name: found.name,
|
|
249
249
|
},
|
|
250
250
|
{ type: 'text', text: renderEntity(found, catalogue, file?.body ?? '') },
|
|
@@ -259,7 +259,7 @@ export const entitiesProvider: ProviderDefinition = defineLocalProvider({
|
|
|
259
259
|
title: 'Read one entity',
|
|
260
260
|
description:
|
|
261
261
|
'Return one entity by id, with its relationships and everything pointing at it. The resource ' +
|
|
262
|
-
'entities://entity/{id} is the same content; this exists for clients that do not read resources.',
|
|
262
|
+
'lanes-entities://entity/{id} is the same content; this exists for clients that do not read resources.',
|
|
263
263
|
inputSchema: z.object({ id: z.string().min(1).describe('Entity id') }),
|
|
264
264
|
redact: keepKeys('id'),
|
|
265
265
|
async handler({ id }, context) {
|
|
@@ -120,7 +120,7 @@ export const writeCapabilities: readonly Capability[] = [
|
|
|
120
120
|
type: 'text',
|
|
121
121
|
text: `${existing === null ? 'Declared' : 'Updated'} "${id}" on ${context.connection.key}.`,
|
|
122
122
|
},
|
|
123
|
-
{ type: 'resource_link', uri: `entities://entity/${id}`, name },
|
|
123
|
+
{ type: 'resource_link', uri: `lanes-entities://entity/${id}`, name },
|
|
124
124
|
],
|
|
125
125
|
};
|
|
126
126
|
},
|
|
@@ -65,7 +65,7 @@ export function createIdentityProvider(options: IdentityProviderOptions): Provid
|
|
|
65
65
|
const entries = options.entries ?? [];
|
|
66
66
|
|
|
67
67
|
return defineLocalProvider({
|
|
68
|
-
id: '
|
|
68
|
+
id: 'lanes_identity',
|
|
69
69
|
name: 'Identity',
|
|
70
70
|
version: '1.0.0',
|
|
71
71
|
description:
|
|
@@ -128,7 +128,7 @@ function render(
|
|
|
128
128
|
// Both flags, spelled out. They are required (ADR-037), so a command
|
|
129
129
|
// missing either is one the owner pastes and watches refuse — and this is
|
|
130
130
|
// handed to an agent, which relays it verbatim.
|
|
131
|
-
const where = `--profile ${profile}${target ? ` --
|
|
131
|
+
const where = `--profile ${profile}${target ? ` --workspace ${target}` : ''}`;
|
|
132
132
|
return (
|
|
133
133
|
`Profile "${profile}" declares no identity.\n\n` +
|
|
134
134
|
'Nothing here says what name or address to use, so do not invent one — ask. ' +
|
|
@@ -172,7 +172,7 @@ function slugify(title: string): string {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
175
|
-
id: '
|
|
175
|
+
id: 'lanes_memory',
|
|
176
176
|
name: 'Memory',
|
|
177
177
|
version: '1.0.0',
|
|
178
178
|
description:
|
|
@@ -212,7 +212,7 @@ export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
|
212
212
|
name: 'entry',
|
|
213
213
|
title: 'Memory entry',
|
|
214
214
|
description: 'One stored memory entry, addressed by its id.',
|
|
215
|
-
uriTemplate: 'memory://entry/{id}',
|
|
215
|
+
uriTemplate: 'lanes-memory://entry/{id}',
|
|
216
216
|
mimeType: 'text/markdown',
|
|
217
217
|
// The address is worth recording and the content is not — the same trade
|
|
218
218
|
// `gmail.get_message` makes with a message id.
|
|
@@ -220,7 +220,7 @@ export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
|
220
220
|
|
|
221
221
|
async list(context) {
|
|
222
222
|
return (await allEntries(context.storage)).map((entry) => ({
|
|
223
|
-
uri: `memory://entry/${encodeURIComponent(entry.id)}`,
|
|
223
|
+
uri: `lanes-memory://entry/${encodeURIComponent(entry.id)}`,
|
|
224
224
|
name: entry.title,
|
|
225
225
|
}));
|
|
226
226
|
},
|
|
@@ -242,7 +242,7 @@ export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
|
242
242
|
name: 'get',
|
|
243
243
|
title: 'Read a memory entry',
|
|
244
244
|
description:
|
|
245
|
-
'Return one entry by id. The resource memory://entry/{id} is the same content; this exists for clients that do not read resources.',
|
|
245
|
+
'Return one entry by id. The resource lanes-memory://entry/{id} is the same content; this exists for clients that do not read resources.',
|
|
246
246
|
inputSchema: z.object({
|
|
247
247
|
id: z.string().min(1).describe('Entry id'),
|
|
248
248
|
}),
|
|
@@ -272,8 +272,26 @@ export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
|
272
272
|
tag: z.string().optional().describe('Restrict to entries carrying this tag'),
|
|
273
273
|
limit: z.number().int().min(1).max(50).optional().describe(`Maximum results (default ${DEFAULT_LIMIT})`),
|
|
274
274
|
}),
|
|
275
|
-
//
|
|
276
|
-
//
|
|
275
|
+
// Kept, and this reverses the rule the rest of this file follows.
|
|
276
|
+
//
|
|
277
|
+
// The old comment here read "a memory query is as revealing as a Gmail
|
|
278
|
+
// search query, and frequently more so", which is true and is an argument
|
|
279
|
+
// about the wrong thing. A search term is not the owner's material: it is
|
|
280
|
+
// what an *agent* went looking for in that material, which is precisely
|
|
281
|
+
// the question this log exists to answer. Withholding it leaves a record
|
|
282
|
+
// saying memory was searched, twice, and matched nothing, with no way to
|
|
283
|
+
// tell a calendar lookup from a rummage through someone's medical notes.
|
|
284
|
+
//
|
|
285
|
+
// The body stays withheld either way — `entry` and `get` keep only `uri`
|
|
286
|
+
// and `id`, so what was *found* is still not in here. This records the
|
|
287
|
+
// question, not the answer.
|
|
288
|
+
//
|
|
289
|
+
// The reason for the old rule was `audit/fanout.ts`: a workspace may ship
|
|
290
|
+
// copies to stdout or an OTLP collector, and those leave the machine. That
|
|
291
|
+
// is a real exposure and it is the operator's to weigh, which is what a
|
|
292
|
+
// declared sink is. It is not a reason to withhold from the durable log on
|
|
293
|
+
// the operator's own disk.
|
|
294
|
+
redact: keepKeys('query', 'tag', 'limit'),
|
|
277
295
|
async handler({ query, tag, limit }, context) {
|
|
278
296
|
const needle = query.toLowerCase();
|
|
279
297
|
const entries = await allEntries(context.storage);
|
|
@@ -308,7 +326,7 @@ export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
|
308
326
|
content: matches.flatMap(({ entry, snippet }) => [
|
|
309
327
|
{
|
|
310
328
|
type: 'resource_link' as const,
|
|
311
|
-
uri: `memory://entry/${encodeURIComponent(entry.id)}`,
|
|
329
|
+
uri: `lanes-memory://entry/${encodeURIComponent(entry.id)}`,
|
|
312
330
|
name: entry.title,
|
|
313
331
|
},
|
|
314
332
|
{
|
|
@@ -352,7 +370,7 @@ export const memoryProvider: ProviderDefinition = defineLocalProvider({
|
|
|
352
370
|
return {
|
|
353
371
|
content: [
|
|
354
372
|
{ type: 'text', text: `Stored memory entry "${entryId}" on ${context.connection.key}.` },
|
|
355
|
-
{ type: 'resource_link', uri: `memory://entry/${entryId}`, name: title },
|
|
373
|
+
{ type: 'resource_link', uri: `lanes-memory://entry/${entryId}`, name: title },
|
|
356
374
|
],
|
|
357
375
|
};
|
|
358
376
|
},
|
|
@@ -137,7 +137,7 @@ export function planFor(
|
|
|
137
137
|
// missing either is one that refuses, or worse, writes a credential into a
|
|
138
138
|
// store the endpoint that asked for it does not read.
|
|
139
139
|
const command =
|
|
140
|
-
`lanes link connect ${manifest.id} --profile ${context.profile} --
|
|
140
|
+
`lanes link connect ${manifest.id} --profile ${context.profile} --workspace ${context.target}` +
|
|
141
141
|
(needsId ? ' --id <name>' : connectionId ? ` --id ${connectionId}` : '');
|
|
142
142
|
|
|
143
143
|
return {
|
|
@@ -92,7 +92,7 @@ export function createSetupProvider(options: SetupProviderOptions): ProviderDefi
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
return defineLocalProvider({
|
|
95
|
-
id: '
|
|
95
|
+
id: 'lanes_setup',
|
|
96
96
|
name: 'Setup',
|
|
97
97
|
version: '1.0.0',
|
|
98
98
|
description:
|
|
@@ -147,7 +147,7 @@ export function createSetupProvider(options: SetupProviderOptions): ProviderDefi
|
|
|
147
147
|
'The console steps, the values needed, and the exact command that connects it. ' +
|
|
148
148
|
'Use this to tell the owner what to run — do not compose the command yourself.',
|
|
149
149
|
inputSchema: z.object({
|
|
150
|
-
id: z.string().min(1).describe('Provider id, as listed by
|
|
150
|
+
id: z.string().min(1).describe('Provider id, as listed by lanes_setup_overview — e.g. "notion"'),
|
|
151
151
|
connection: z
|
|
152
152
|
.string()
|
|
153
153
|
.optional()
|
|
@@ -167,7 +167,7 @@ export function createSetupProvider(options: SetupProviderOptions): ProviderDefi
|
|
|
167
167
|
{
|
|
168
168
|
type: 'text',
|
|
169
169
|
text:
|
|
170
|
-
`No provider "${id}". Call
|
|
170
|
+
`No provider "${id}". Call lanes_setup_overview for the ids this endpoint knows.`,
|
|
171
171
|
},
|
|
172
172
|
],
|
|
173
173
|
isError: true,
|
|
@@ -110,7 +110,7 @@ export function createSkillsProvider(options: SkillsProviderOptions): ProviderDe
|
|
|
110
110
|
const manage = options.store ? managementCapabilities(options.store, options.onChange) : [];
|
|
111
111
|
|
|
112
112
|
return defineLocalProvider({
|
|
113
|
-
id: '
|
|
113
|
+
id: 'lanes_skills',
|
|
114
114
|
name: 'Skills',
|
|
115
115
|
version: '1.0.0',
|
|
116
116
|
description:
|
|
@@ -246,7 +246,7 @@ function managementCapabilities(
|
|
|
246
246
|
content: [
|
|
247
247
|
{
|
|
248
248
|
type: 'text',
|
|
249
|
-
text: `Stored skill "${skill.name}" on ${context.connection.key}. It is available as the prompt "
|
|
249
|
+
text: `Stored skill "${skill.name}" on ${context.connection.key}. It is available as the prompt "lanes_skills_${skill.name}" where policy allows it.`,
|
|
250
250
|
},
|
|
251
251
|
],
|
|
252
252
|
};
|
|
@@ -105,12 +105,12 @@ export const slack = defineProvider({
|
|
|
105
105
|
'Open "OAuth & Permissions" and add the scopes you need under USER TOKEN SCOPES — not Bot Token Scopes; the MCP server reads the user token. The full set this provider asks for in the browser is listed in https://lanes.sh/docs/link/slack.',
|
|
106
106
|
'Choose "Install to Workspace" and approve. A Slack admin may have to approve it for you.',
|
|
107
107
|
'Copy the "User OAuth Token". It starts with xoxp- — not the bot token, which starts with xoxb- and will not work here.',
|
|
108
|
-
'The token does not expire unless you enable token rotation on the app. If you rotate or reinstall, run: lanes link connect slack --profile personal --
|
|
108
|
+
'The token does not expire unless you enable token rotation on the app. If you rotate or reinstall, run: lanes link connect slack --profile personal --workspace local --auth pasted_token --replace.',
|
|
109
109
|
],
|
|
110
110
|
troubleshooting:
|
|
111
111
|
'Slack refused the token. The usual causes are a bot token (xoxb-) pasted where the user token (xoxp-) belongs, ' +
|
|
112
112
|
'a scope missing from USER TOKEN SCOPES, or an app that was reinstalled since — reinstalling mints a new token. ' +
|
|
113
|
-
'Copy the User OAuth Token from https://api.slack.com/apps and re-run: lanes link connect slack --profile personal --
|
|
113
|
+
'Copy the User OAuth Token from https://api.slack.com/apps and re-run: lanes link connect slack --profile personal --workspace local --auth pasted_token --replace.',
|
|
114
114
|
prompts: [
|
|
115
115
|
{
|
|
116
116
|
key: 'token',
|
|
@@ -57,7 +57,7 @@ function matches(task: Task, needle: string): boolean {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export const tasksProvider: ProviderDefinition = defineLocalProvider({
|
|
60
|
-
id: '
|
|
60
|
+
id: 'lanes_tasks',
|
|
61
61
|
name: 'Tasks',
|
|
62
62
|
version: '1.0.0',
|
|
63
63
|
description:
|
|
@@ -92,13 +92,13 @@ export const tasksProvider: ProviderDefinition = defineLocalProvider({
|
|
|
92
92
|
name: 'task',
|
|
93
93
|
title: 'Task',
|
|
94
94
|
description: 'One task, addressed by its id.',
|
|
95
|
-
uriTemplate: 'tasks://task/{id}',
|
|
95
|
+
uriTemplate: 'lanes-tasks://task/{id}',
|
|
96
96
|
mimeType: 'text/markdown',
|
|
97
97
|
redact: keepKeys('uri'),
|
|
98
98
|
|
|
99
99
|
async list(context) {
|
|
100
100
|
return (await allTasks(context.storage)).map((task) => ({
|
|
101
|
-
uri: `tasks://task/${encodeURIComponent(task.id)}`,
|
|
101
|
+
uri: `lanes-tasks://task/${encodeURIComponent(task.id)}`,
|
|
102
102
|
name: task.title,
|
|
103
103
|
}));
|
|
104
104
|
},
|
|
@@ -170,7 +170,7 @@ export const tasksProvider: ProviderDefinition = defineLocalProvider({
|
|
|
170
170
|
...shown.flatMap((task) => [
|
|
171
171
|
{
|
|
172
172
|
type: 'resource_link' as const,
|
|
173
|
-
uri: `tasks://task/${encodeURIComponent(task.id)}`,
|
|
173
|
+
uri: `lanes-tasks://task/${encodeURIComponent(task.id)}`,
|
|
174
174
|
name: task.title,
|
|
175
175
|
},
|
|
176
176
|
{ type: 'text' as const, text: `${task.id} ${line(task)}` },
|
|
@@ -193,7 +193,7 @@ export const tasksProvider: ProviderDefinition = defineLocalProvider({
|
|
|
193
193
|
name: 'get',
|
|
194
194
|
title: 'Read a task',
|
|
195
195
|
description:
|
|
196
|
-
'Return one task by id, notes included. The resource tasks://task/{id} is the same content; this exists for clients that do not read resources.',
|
|
196
|
+
'Return one task by id, notes included. The resource lanes-tasks://task/{id} is the same content; this exists for clients that do not read resources.',
|
|
197
197
|
inputSchema: z.object({ id: z.string().min(1).describe('Task id') }),
|
|
198
198
|
redact: keepKeys('id'),
|
|
199
199
|
async handler({ id }, context) {
|
|
@@ -261,7 +261,7 @@ export const tasksProvider: ProviderDefinition = defineLocalProvider({
|
|
|
261
261
|
type: 'text',
|
|
262
262
|
text: `${existing ? 'Replaced' : 'Added'} task "${taskId}" on ${context.connection.key}.`,
|
|
263
263
|
},
|
|
264
|
-
{ type: 'resource_link', uri: `tasks://task/${taskId}`, name: title },
|
|
264
|
+
{ type: 'resource_link', uri: `lanes-tasks://task/${taskId}`, name: title },
|
|
265
265
|
],
|
|
266
266
|
};
|
|
267
267
|
},
|