@assemblyline-agents/margins 2.0.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/pairing.d.ts +19 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +191 -0
- package/dist/pairing.js.map +1 -0
- package/package.json +50 -0
- package/skills/margins/SKILL.md +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenEve contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @assemblyline-agents/margins
|
|
2
|
+
|
|
3
|
+
Official Assembly Line connection plugin for resource-scoped collaborative
|
|
4
|
+
Markdown in Margins. A binding can cover one page, one folder and its
|
|
5
|
+
descendants, or the whole workspace.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
assembly-line add margins agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
No developer OAuth registration or long-lived token environment variable is
|
|
14
|
+
required for the hosted service. In Margins, open the agent invite for the
|
|
15
|
+
page, folder, or workspace, choose **Comment and suggest** or **Edit directly**,
|
|
16
|
+
and paste the generated one-time packet into the agent's chat. Assembly Line
|
|
17
|
+
advertises `margins__pair`, redeems the packet on the host, and stores the
|
|
18
|
+
returned rotating access and refresh credentials outside the model sandbox.
|
|
19
|
+
|
|
20
|
+
For self-hosted Margins, set `MARGINS_MCP_URL` and optionally
|
|
21
|
+
`MARGINS_ISSUER`. Configure stable collaborator identity with
|
|
22
|
+
`MARGINS_AGENT_ID`, `MARGINS_AGENT_NAME`, `MARGINS_AGENT_RUNTIME`, and
|
|
23
|
+
`MARGINS_AGENT_HARNESS`, or pass their equivalents to
|
|
24
|
+
`defineMarginsConnection()`.
|
|
25
|
+
|
|
26
|
+
The generated connection is read-only at Assembly Line's outer policy layer.
|
|
27
|
+
Margins still enforces the packet's own scope and permission. Enable reviewed
|
|
28
|
+
mutations explicitly if the agent should comment, suggest, create pages, or
|
|
29
|
+
edit Markdown:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { defineMarginsConnection } from "@assemblyline-agents/margins";
|
|
33
|
+
|
|
34
|
+
export default defineMarginsConnection({
|
|
35
|
+
agentId: "research-editor",
|
|
36
|
+
agentName: "Research Editor",
|
|
37
|
+
access: {
|
|
38
|
+
read: true,
|
|
39
|
+
write: { approval: "always" }
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Comments and suggestions are classified as writes because they mutate shared
|
|
45
|
+
workspace state. Margins applies its own permission check after Assembly Line's
|
|
46
|
+
approval gate; a **Comment and suggest** binding can never edit canonical
|
|
47
|
+
Markdown or create a page.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ConnectionAuthDefinition, type McpPluginConnectionOptions } from "@assemblyline-agents/core";
|
|
2
|
+
export interface MarginsConnectionOptions extends Omit<McpPluginConnectionOptions, "auth"> {
|
|
3
|
+
/** Override the refresh-capable authorization contract. Keep the default when using binding packets. */
|
|
4
|
+
auth?: ConnectionAuthDefinition | false;
|
|
5
|
+
/** Margins deployment origin. Defaults to the MCP endpoint's origin. */
|
|
6
|
+
issuer?: string;
|
|
7
|
+
/** Stable agent id recorded in Margins comments, suggestions, edits, and presence. */
|
|
8
|
+
agentId?: string;
|
|
9
|
+
/** Human-readable agent name shown to Margins collaborators. */
|
|
10
|
+
agentName?: string;
|
|
11
|
+
/** Agent runtime recorded with the binding. Defaults to assembly-line. */
|
|
12
|
+
agentRuntime?: string;
|
|
13
|
+
/** Harness recorded with the binding. Defaults to assembly-line. */
|
|
14
|
+
agentHarness?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function defineMarginsConnection(options: MarginsConnectionOptions): import("@assemblyline-agents/core").McpHttpClientConnectionDefinition;
|
|
17
|
+
export declare const assemblyLinePlugin: import("@assemblyline-agents/core").PluginModule;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,2BAA2B,CAAC;AAKnC,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC;IACxF,wGAAwG;IACxG,IAAI,CAAC,EAAE,wBAAwB,GAAG,KAAK,CAAC;IACxC,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,yEAiCxE;AAED,eAAO,MAAM,kBAAkB,kDAA0C,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { connectionPluginMetadata, defineMcpPluginConnection, defineOAuthAuthorization, definePlugin } from "@assemblyline-agents/core";
|
|
2
|
+
import { createMarginsPairingRedeemer } from "./pairing.js";
|
|
3
|
+
const PLUGIN = connectionPluginMetadata("margins");
|
|
4
|
+
export function defineMarginsConnection(options) {
|
|
5
|
+
const { auth, issuer, agentId, agentName, agentRuntime, agentHarness, ...connection } = options;
|
|
6
|
+
const definition = defineMcpPluginConnection(PLUGIN, {
|
|
7
|
+
...connection,
|
|
8
|
+
auth: auth === false
|
|
9
|
+
? false
|
|
10
|
+
: auth ?? marginsAuthorization({
|
|
11
|
+
...(issuer ? { issuer } : {}),
|
|
12
|
+
...(connection.url ? { mcpUrl: connection.url } : {})
|
|
13
|
+
})
|
|
14
|
+
});
|
|
15
|
+
definition.redeemPairingCode = createMarginsPairingRedeemer(({ env }) => ({
|
|
16
|
+
origin: normalizeIssuer(issuer
|
|
17
|
+
?? env.MARGINS_ISSUER
|
|
18
|
+
?? connection.url
|
|
19
|
+
?? env.MARGINS_MCP_URL
|
|
20
|
+
?? definition.url),
|
|
21
|
+
agentId: cleanIdentity(agentId ?? env.MARGINS_AGENT_ID) ?? "assembly-line-agent",
|
|
22
|
+
agentName: cleanIdentity(agentName ?? env.MARGINS_AGENT_NAME) ?? "Assembly Line Agent",
|
|
23
|
+
runtime: cleanIdentity(agentRuntime ?? env.MARGINS_AGENT_RUNTIME) ?? "assembly-line",
|
|
24
|
+
harness: cleanIdentity(agentHarness ?? env.MARGINS_AGENT_HARNESS) ?? "assembly-line"
|
|
25
|
+
}));
|
|
26
|
+
return definition;
|
|
27
|
+
}
|
|
28
|
+
export const assemblyLinePlugin = definePlugin({ connections: [PLUGIN] });
|
|
29
|
+
function marginsAuthorization(input) {
|
|
30
|
+
return ({ env }) => {
|
|
31
|
+
const resolvedIssuer = normalizeIssuer(input.issuer
|
|
32
|
+
?? env.MARGINS_ISSUER
|
|
33
|
+
?? input.mcpUrl
|
|
34
|
+
?? env.MARGINS_MCP_URL
|
|
35
|
+
?? PLUGIN.defaultUrl);
|
|
36
|
+
return defineOAuthAuthorization({
|
|
37
|
+
displayName: "Margins",
|
|
38
|
+
authorizeUrl: new URL("/?open=agents", resolvedIssuer).toString(),
|
|
39
|
+
tokenUrl: new URL("/api/agent-bindings/token", resolvedIssuer).toString(),
|
|
40
|
+
clientId: "assembly-line",
|
|
41
|
+
scopes: ["margins"],
|
|
42
|
+
pkce: true
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function normalizeIssuer(value) {
|
|
47
|
+
return new URL(value).origin;
|
|
48
|
+
}
|
|
49
|
+
function cleanIdentity(value) {
|
|
50
|
+
const clean = value?.trim().replace(/[\u0000-\u001f\u007f]/gu, "");
|
|
51
|
+
return clean ? clean.slice(0, 160) : undefined;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConnectionPairingRedeemer, ConnectionResolverContext } from "@assemblyline-agents/core";
|
|
2
|
+
export interface MarginsBindingClaim {
|
|
3
|
+
origin: string;
|
|
4
|
+
bindingId: string;
|
|
5
|
+
claimToken: string;
|
|
6
|
+
}
|
|
7
|
+
export interface MarginsPairingIdentity {
|
|
8
|
+
origin: string;
|
|
9
|
+
agentId: string;
|
|
10
|
+
agentName: string;
|
|
11
|
+
runtime: string;
|
|
12
|
+
harness: string;
|
|
13
|
+
}
|
|
14
|
+
type MarginsPairingResolver = (context: ConnectionResolverContext) => MarginsPairingIdentity;
|
|
15
|
+
/** Exchange a short-lived Margins UI claim for host-stored access and refresh tokens. */
|
|
16
|
+
export declare function createMarginsPairingRedeemer(resolvePairing: MarginsPairingResolver): ConnectionPairingRedeemer;
|
|
17
|
+
export declare function parseMarginsBindingPacket(packet: string): MarginsBindingClaim;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=pairing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EAG1B,MAAM,2BAA2B,CAAC;AAOnC,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,sBAAsB,GAAG,CAAC,OAAO,EAAE,yBAAyB,KAAK,sBAAsB,CAAC;AAE7F,yFAAyF;AACzF,wBAAgB,4BAA4B,CAAC,cAAc,EAAE,sBAAsB,GAAG,yBAAyB,CAkC9G;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAe7E"}
|
package/dist/pairing.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const MAX_PACKET_CHARS = 64 * 1024;
|
|
2
|
+
const MAX_PACKET_FIELD_CHARS = 4 * 1024;
|
|
3
|
+
const MAX_RESPONSE_CHARS = 64 * 1024;
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
5
|
+
/** Exchange a short-lived Margins UI claim for host-stored access and refresh tokens. */
|
|
6
|
+
export function createMarginsPairingRedeemer(resolvePairing) {
|
|
7
|
+
return async ({ code }, context) => {
|
|
8
|
+
const claim = parseMarginsBindingPacket(code);
|
|
9
|
+
const pairing = resolvePairing(context);
|
|
10
|
+
const expectedOrigin = normalizedSecureOrigin(pairing.origin);
|
|
11
|
+
if (claim.origin !== expectedOrigin) {
|
|
12
|
+
throw new Error("The Margins binding packet belongs to a different Margins deployment.");
|
|
13
|
+
}
|
|
14
|
+
const response = await fetchWithTimeout(new URL("/api/agent-binding-pairing-requests", `${expectedOrigin}/`), {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: {
|
|
17
|
+
accept: "application/json",
|
|
18
|
+
"content-type": "application/json"
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
bindingId: claim.bindingId,
|
|
22
|
+
claimToken: claim.claimToken,
|
|
23
|
+
agentId: pairing.agentId,
|
|
24
|
+
agentName: pairing.agentName,
|
|
25
|
+
runtime: pairing.runtime,
|
|
26
|
+
harness: pairing.harness
|
|
27
|
+
}),
|
|
28
|
+
redirect: "error"
|
|
29
|
+
});
|
|
30
|
+
const payload = await responseJson(response);
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`Margins rejected the binding packet (HTTP ${response.status}). Create a fresh packet and try again.`);
|
|
33
|
+
}
|
|
34
|
+
return tokenResultForBinding(payload, claim, pairing);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function parseMarginsBindingPacket(packet) {
|
|
38
|
+
const input = packet.trim();
|
|
39
|
+
if (!input || input.length > MAX_PACKET_CHARS) {
|
|
40
|
+
throw new Error("A valid Margins binding packet is required.");
|
|
41
|
+
}
|
|
42
|
+
for (const line of input.split(/\r?\n/u)) {
|
|
43
|
+
const match = /^\s*POST\s+(\S+)\s+(\{.*\})\s*$/u.exec(line);
|
|
44
|
+
if (!match?.[1] || !match[2])
|
|
45
|
+
continue;
|
|
46
|
+
const origin = pairingEndpointOrigin(match[1]);
|
|
47
|
+
const body = parseJsonObject(match[2]);
|
|
48
|
+
const bindingId = boundedString(body?.bindingId);
|
|
49
|
+
const claimToken = boundedString(body?.claimToken ?? body?.claim);
|
|
50
|
+
if (origin && bindingId && claimToken)
|
|
51
|
+
return { origin, bindingId, claimToken };
|
|
52
|
+
}
|
|
53
|
+
throw new Error("The Margins binding packet is missing its binding id, one-time claim token, or pairing URL.");
|
|
54
|
+
}
|
|
55
|
+
function pairingEndpointOrigin(value) {
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(value);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
if (url.username
|
|
64
|
+
|| url.password
|
|
65
|
+
|| url.search
|
|
66
|
+
|| url.hash
|
|
67
|
+
|| url.pathname !== "/api/agent-binding-pairing-requests") {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
return normalizedSecureOrigin(url.origin);
|
|
71
|
+
}
|
|
72
|
+
function tokenResultForBinding(payload, claim, pairing) {
|
|
73
|
+
const token = boundedString(payload.access_token);
|
|
74
|
+
const refreshToken = boundedString(payload.refresh_token);
|
|
75
|
+
const tokenType = boundedString(payload.token_type) ?? "Bearer";
|
|
76
|
+
const grantedScope = boundedString(payload.scope);
|
|
77
|
+
const expiresIn = numericValue(payload.expires_in);
|
|
78
|
+
const bindingId = boundedString(payload.margins_binding_id);
|
|
79
|
+
const workspaceId = boundedString(payload.margins_workspace_id);
|
|
80
|
+
const bindingScope = boundedString(payload.margins_binding_scope);
|
|
81
|
+
const scopeId = boundedString(payload.margins_binding_scope_id);
|
|
82
|
+
const scopePath = boundedString(payload.margins_binding_scope_path);
|
|
83
|
+
const permission = boundedString(payload.margins_binding_permission);
|
|
84
|
+
const agentId = boundedString(payload.margins_agent_id);
|
|
85
|
+
const agentName = boundedString(payload.margins_agent_name);
|
|
86
|
+
if (!token
|
|
87
|
+
|| !refreshToken
|
|
88
|
+
|| tokenType.toLowerCase() !== "bearer"
|
|
89
|
+
|| grantedScope !== "margins"
|
|
90
|
+
|| !expiresIn
|
|
91
|
+
|| expiresIn <= 0
|
|
92
|
+
|| bindingId !== claim.bindingId
|
|
93
|
+
|| !workspaceId
|
|
94
|
+
|| !scopeId
|
|
95
|
+
|| !["file", "folder", "workspace"].includes(bindingScope ?? "")
|
|
96
|
+
|| (bindingScope === "workspace" && scopeId !== workspaceId)
|
|
97
|
+
|| !["suggest", "edit"].includes(permission ?? "")
|
|
98
|
+
|| agentId !== pairing.agentId
|
|
99
|
+
|| agentName !== pairing.agentName) {
|
|
100
|
+
throw new Error("Margins activated a different or incomplete agent binding.");
|
|
101
|
+
}
|
|
102
|
+
const credential = {
|
|
103
|
+
margins_binding_id: bindingId,
|
|
104
|
+
margins_workspace_id: workspaceId,
|
|
105
|
+
margins_binding_scope: bindingScope,
|
|
106
|
+
margins_binding_scope_id: scopeId,
|
|
107
|
+
margins_binding_permission: permission,
|
|
108
|
+
margins_agent_id: agentId,
|
|
109
|
+
margins_agent_name: agentName
|
|
110
|
+
};
|
|
111
|
+
if (scopePath)
|
|
112
|
+
credential.margins_binding_scope_path = scopePath;
|
|
113
|
+
const result = {
|
|
114
|
+
token,
|
|
115
|
+
refreshToken,
|
|
116
|
+
tokenType: "Bearer",
|
|
117
|
+
scopes: ["margins"],
|
|
118
|
+
credential
|
|
119
|
+
};
|
|
120
|
+
result.expiresAt = Date.now() + expiresIn * 1000;
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
async function fetchWithTimeout(url, init) {
|
|
124
|
+
try {
|
|
125
|
+
return await fetch(url, {
|
|
126
|
+
...init,
|
|
127
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new Error("Could not reach the configured Margins deployment.");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function responseJson(response) {
|
|
135
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
136
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_RESPONSE_CHARS)
|
|
137
|
+
return {};
|
|
138
|
+
try {
|
|
139
|
+
const text = await response.text();
|
|
140
|
+
if (text.length > MAX_RESPONSE_CHARS)
|
|
141
|
+
return {};
|
|
142
|
+
return parseJsonObject(text) ?? {};
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function normalizedSecureOrigin(value) {
|
|
149
|
+
let url;
|
|
150
|
+
try {
|
|
151
|
+
url = new URL(value);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
throw new Error("Margins pairing requires a valid API URL.");
|
|
155
|
+
}
|
|
156
|
+
if (url.username || url.password || url.origin === "null") {
|
|
157
|
+
throw new Error("Margins pairing requires a valid API origin.");
|
|
158
|
+
}
|
|
159
|
+
const host = url.hostname.toLowerCase();
|
|
160
|
+
const loopback = host === "localhost"
|
|
161
|
+
|| host === "127.0.0.1"
|
|
162
|
+
|| host === "::1"
|
|
163
|
+
|| host === "[::1]"
|
|
164
|
+
|| host.endsWith(".localhost");
|
|
165
|
+
if (url.protocol !== "https:" && !loopback) {
|
|
166
|
+
throw new Error("Margins binding credentials may only be exchanged over HTTPS or loopback.");
|
|
167
|
+
}
|
|
168
|
+
return url.origin;
|
|
169
|
+
}
|
|
170
|
+
function parseJsonObject(value) {
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(value);
|
|
173
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
174
|
+
? parsed
|
|
175
|
+
: undefined;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function boundedString(value) {
|
|
182
|
+
if (typeof value !== "string")
|
|
183
|
+
return undefined;
|
|
184
|
+
const clean = value.trim();
|
|
185
|
+
return clean && clean.length <= MAX_PACKET_FIELD_CHARS ? clean : undefined;
|
|
186
|
+
}
|
|
187
|
+
function numericValue(value) {
|
|
188
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
189
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=pairing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@assemblyline-agents/margins",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@assemblyline-agents/core": "2.0.0"
|
|
13
|
+
},
|
|
14
|
+
"description": "Official Assembly Line connection plugin for resource-scoped collaborative Markdown in Margins.",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"skills"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.19.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/jasonbadeaux/assembly-line.git",
|
|
26
|
+
"directory": "packages/margins"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/jasonbadeaux/assembly-line/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/jasonbadeaux/assembly-line#readme",
|
|
32
|
+
"author": "Assembly Line contributors",
|
|
33
|
+
"keywords": [
|
|
34
|
+
"assembly-line",
|
|
35
|
+
"ai",
|
|
36
|
+
"agents",
|
|
37
|
+
"margins",
|
|
38
|
+
"markdown",
|
|
39
|
+
"collaboration",
|
|
40
|
+
"mcp",
|
|
41
|
+
"plugin"
|
|
42
|
+
],
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.json",
|
|
48
|
+
"check": "tsc -p tsconfig.json --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: margins
|
|
3
|
+
description: Collaboratively read, comment on, suggest changes to, and edit Markdown pages through a resource-scoped Margins binding.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Margins
|
|
7
|
+
|
|
8
|
+
Margins is a collaborative Markdown workspace. A connection is bound to one
|
|
9
|
+
page, one folder and its descendants, or one whole workspace. Never assume a
|
|
10
|
+
broader scope than `margins_status` reports.
|
|
11
|
+
|
|
12
|
+
If the user pastes a Margins binding packet, use connection discovery to find
|
|
13
|
+
`margins__pair` and pass the full packet through its `secret` field. Do not run
|
|
14
|
+
the packet in a shell, repeat its one-time claim token, or put the packet in a
|
|
15
|
+
file. After pairing succeeds, call `margins_status` with the expected binding
|
|
16
|
+
fields embedded in the packet. Stop if it reports `wrong_binding`.
|
|
17
|
+
|
|
18
|
+
Read progressively:
|
|
19
|
+
|
|
20
|
+
- Call `margins_map` for folder or workspace bindings to list reachable pages.
|
|
21
|
+
- Call `margins_context` with `summary=true` before requesting line ranges.
|
|
22
|
+
- Use `margins_review` for open comments, suggestions, and anchors.
|
|
23
|
+
- Use `margins_graph` for Markdown link structure and `margins_library` for the
|
|
24
|
+
reachable tree. `margins_history` is workspace-only.
|
|
25
|
+
|
|
26
|
+
For collaborative mutations, refetch the relevant context immediately before
|
|
27
|
+
acting and pass the current authority head so stale line ranges fail safely.
|
|
28
|
+
Use `margins_comment` for discussion and `margins_suggest` when a human should
|
|
29
|
+
review a proposed patch. Use `margins_edit` only when the user asked for a
|
|
30
|
+
direct change and the binding grants edit access. `margins_create_file` is
|
|
31
|
+
available only for edit-capable folder or workspace bindings.
|
|
32
|
+
|
|
33
|
+
Assembly Line classifies comments, suggestions, page creation, and direct edits
|
|
34
|
+
as writes. Its approval policy is an outer gate; Margins independently enforces
|
|
35
|
+
the binding's file/folder/workspace boundary and its suggest/edit permission.
|
|
36
|
+
Never work around a missing tool by using a durable people-share URL or by
|
|
37
|
+
asking the user for bearer credentials.
|