iosm-cli 0.2.14 → 0.2.16
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 +50 -0
- package/README.md +18 -1
- package/dist/cli/args.d.ts +2 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-profiles.d.ts.map +1 -1
- package/dist/core/agent-profiles.js +3 -0
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +26 -10
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-teams.d.ts.map +1 -1
- package/dist/core/agent-teams.js +12 -9
- package/dist/core/agent-teams.js.map +1 -1
- package/dist/core/checkpoint/fs-checkpoint.d.ts +14 -0
- package/dist/core/checkpoint/fs-checkpoint.d.ts.map +1 -0
- package/dist/core/checkpoint/fs-checkpoint.js +211 -0
- package/dist/core/checkpoint/fs-checkpoint.js.map +1 -0
- package/dist/core/command-dispatcher.d.ts +2 -0
- package/dist/core/command-dispatcher.d.ts.map +1 -1
- package/dist/core/command-dispatcher.js +78 -13
- package/dist/core/command-dispatcher.js.map +1 -1
- package/dist/core/mcp/cli.d.ts.map +1 -1
- package/dist/core/mcp/cli.js +26 -0
- package/dist/core/mcp/cli.js.map +1 -1
- package/dist/core/mcp/config.d.ts.map +1 -1
- package/dist/core/mcp/config.js +55 -0
- package/dist/core/mcp/config.js.map +1 -1
- package/dist/core/mcp/index.d.ts +1 -1
- package/dist/core/mcp/index.d.ts.map +1 -1
- package/dist/core/mcp/index.js.map +1 -1
- package/dist/core/mcp/runtime.d.ts +3 -1
- package/dist/core/mcp/runtime.d.ts.map +1 -1
- package/dist/core/mcp/runtime.js +21 -2
- package/dist/core/mcp/runtime.js.map +1 -1
- package/dist/core/mcp/types.d.ts +30 -2
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/mcp/types.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +100 -2
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/policy/engine.d.ts +77 -0
- package/dist/core/policy/engine.d.ts.map +1 -0
- package/dist/core/policy/engine.js +614 -0
- package/dist/core/policy/engine.js.map +1 -0
- package/dist/core/policy/index.d.ts +2 -0
- package/dist/core/policy/index.d.ts.map +1 -0
- package/dist/core/policy/index.js +2 -0
- package/dist/core/policy/index.js.map +1 -0
- package/dist/core/sandbox/executor.d.ts +13 -0
- package/dist/core/sandbox/executor.d.ts.map +1 -0
- package/dist/core/sandbox/executor.js +64 -0
- package/dist/core/sandbox/executor.js.map +1 -0
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/security/index.d.ts +3 -0
- package/dist/core/security/index.d.ts.map +1 -0
- package/dist/core/security/index.js +3 -0
- package/dist/core/security/index.js.map +1 -0
- package/dist/core/security/source-security.d.ts +43 -0
- package/dist/core/security/source-security.d.ts.map +1 -0
- package/dist/core/security/source-security.js +94 -0
- package/dist/core/security/source-security.js.map +1 -0
- package/dist/core/security/trust-ledger.d.ts +24 -0
- package/dist/core/security/trust-ledger.d.ts.map +1 -0
- package/dist/core/security/trust-ledger.js +66 -0
- package/dist/core/security/trust-ledger.js.map +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +128 -15
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +22 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/task-plan.d.ts +1 -0
- package/dist/core/task-plan.d.ts.map +1 -1
- package/dist/core/task-plan.js +103 -0
- package/dist/core/task-plan.js.map +1 -1
- package/dist/core/tools/apply-patch.d.ts +29 -0
- package/dist/core/tools/apply-patch.d.ts.map +1 -0
- package/dist/core/tools/apply-patch.js +167 -0
- package/dist/core/tools/apply-patch.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +15 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/git-common.d.ts.map +1 -1
- package/dist/core/tools/git-common.js +15 -1
- package/dist/core/tools/git-common.js.map +1 -1
- package/dist/core/tools/index.d.ts +20 -2
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +85 -25
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/permissions.d.ts +16 -0
- package/dist/core/tools/permissions.d.ts.map +1 -1
- package/dist/core/tools/permissions.js +34 -1
- package/dist/core/tools/permissions.js.map +1 -1
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +68 -24
- package/dist/core/tools/task.js.map +1 -1
- package/dist/core/tools/tool-search.d.ts +24 -0
- package/dist/core/tools/tool-search.d.ts.map +1 -0
- package/dist/core/tools/tool-search.js +85 -0
- package/dist/core/tools/tool-search.js.map +1 -0
- package/dist/core/tools/tool-suggest.d.ts +18 -0
- package/dist/core/tools/tool-suggest.d.ts.map +1 -0
- package/dist/core/tools/tool-suggest.js +94 -0
- package/dist/core/tools/tool-suggest.js.map +1 -0
- package/dist/core/tools/verification-runner.d.ts.map +1 -1
- package/dist/core/tools/verification-runner.js +15 -1
- package/dist/core/tools/verification-runner.js.map +1 -1
- package/dist/core/unified-exec.d.ts +39 -0
- package/dist/core/unified-exec.d.ts.map +1 -0
- package/dist/core/unified-exec.js +286 -0
- package/dist/core/unified-exec.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +93 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/acp-mode.d.ts +17 -0
- package/dist/modes/acp/acp-mode.d.ts.map +1 -0
- package/dist/modes/acp/acp-mode.js +352 -0
- package/dist/modes/acp/acp-mode.js.map +1 -0
- package/dist/modes/index.d.ts +2 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +217 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +159 -72
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +25 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +33 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +13 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +189 -28
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +54 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/telegram/telegram-bridge-mode.js +51 -22
- package/dist/modes/telegram/telegram-bridge-mode.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +96 -9
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/README.md +2 -0
- package/docs/acp-rpc-mapping.md +38 -0
- package/docs/configuration.generated.md +81 -0
- package/docs/configuration.md +7 -0
- package/package.json +4 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { TrustSourceType } from "./trust-ledger.js";
|
|
2
|
+
export interface SecurityConsentRequest {
|
|
3
|
+
action: "install" | "update" | "download";
|
|
4
|
+
sourceType: TrustSourceType;
|
|
5
|
+
source: string;
|
|
6
|
+
identity: string;
|
|
7
|
+
host: string;
|
|
8
|
+
fingerprint: string;
|
|
9
|
+
reason: "new-source" | "fingerprint-change";
|
|
10
|
+
previousFingerprint?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SourceVerificationInput {
|
|
13
|
+
action: "install" | "update" | "download";
|
|
14
|
+
sourceType: TrustSourceType;
|
|
15
|
+
source: string;
|
|
16
|
+
identity: string;
|
|
17
|
+
host: string;
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
allowOverride?: boolean;
|
|
20
|
+
allowPrompt?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface SourceVerificationResult {
|
|
23
|
+
approved: boolean;
|
|
24
|
+
reusedApproval: boolean;
|
|
25
|
+
reason: "already-trusted" | "new-source-approved" | "fingerprint-reapproved";
|
|
26
|
+
}
|
|
27
|
+
export declare class SourceSecurityManager {
|
|
28
|
+
private readonly ledger;
|
|
29
|
+
private readonly allowedHosts;
|
|
30
|
+
private readonly consentProvider?;
|
|
31
|
+
constructor(options: {
|
|
32
|
+
agentDir: string;
|
|
33
|
+
allowedHosts?: string[];
|
|
34
|
+
consentProvider?: (request: SecurityConsentRequest) => Promise<boolean>;
|
|
35
|
+
});
|
|
36
|
+
getLedgerPath(): string;
|
|
37
|
+
getAllowedHosts(): string[];
|
|
38
|
+
verify(input: SourceVerificationInput): Promise<SourceVerificationResult>;
|
|
39
|
+
private requestConsent;
|
|
40
|
+
private buildKey;
|
|
41
|
+
private assertHostAllowed;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=source-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-security.d.ts","sourceRoot":"","sources":["../../../src/core/security/source-security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAa1E,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC1C,UAAU,EAAE,eAAe,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,GAAG,oBAAoB,CAAC;IAC5C,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACvC,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC1C,UAAU,EAAE,eAAe,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,iBAAiB,GAAG,qBAAqB,GAAG,wBAAwB,CAAC;CAC7E;AAED,qBAAa,qBAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAwD;gBAE7E,OAAO,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KACxE;IASD,aAAa,IAAI,MAAM;IAIvB,eAAe,IAAI,MAAM,EAAE;IAIrB,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;YAwDjE,cAAc;IAO5B,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,iBAAiB;CASzB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { TrustLedger } from "./trust-ledger.js";
|
|
2
|
+
const DEFAULT_ALLOWED_HOSTS = [
|
|
3
|
+
"registry.npmjs.org",
|
|
4
|
+
"npmjs.org",
|
|
5
|
+
"github.com",
|
|
6
|
+
"gitlab.com",
|
|
7
|
+
"bitbucket.org",
|
|
8
|
+
"api.github.com",
|
|
9
|
+
"raw.githubusercontent.com",
|
|
10
|
+
];
|
|
11
|
+
export class SourceSecurityManager {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.ledger = new TrustLedger(options.agentDir);
|
|
14
|
+
const normalizedHosts = (options.allowedHosts ?? DEFAULT_ALLOWED_HOSTS)
|
|
15
|
+
.map((host) => host.trim().toLowerCase())
|
|
16
|
+
.filter((host) => host.length > 0);
|
|
17
|
+
this.allowedHosts = new Set(normalizedHosts);
|
|
18
|
+
this.consentProvider = options.consentProvider;
|
|
19
|
+
}
|
|
20
|
+
getLedgerPath() {
|
|
21
|
+
return this.ledger.getPath();
|
|
22
|
+
}
|
|
23
|
+
getAllowedHosts() {
|
|
24
|
+
return [...this.allowedHosts];
|
|
25
|
+
}
|
|
26
|
+
async verify(input) {
|
|
27
|
+
this.assertHostAllowed(input.host);
|
|
28
|
+
const key = this.buildKey(input.sourceType, input.identity);
|
|
29
|
+
const existing = this.ledger.get(key);
|
|
30
|
+
if (existing && existing.fingerprint === input.fingerprint) {
|
|
31
|
+
return {
|
|
32
|
+
approved: true,
|
|
33
|
+
reusedApproval: true,
|
|
34
|
+
reason: "already-trusted",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const reason = existing ? "fingerprint-change" : "new-source";
|
|
38
|
+
const approved = input.allowOverride === true
|
|
39
|
+
? true
|
|
40
|
+
: input.allowPrompt === false
|
|
41
|
+
? false
|
|
42
|
+
: await this.requestConsent({
|
|
43
|
+
action: input.action,
|
|
44
|
+
sourceType: input.sourceType,
|
|
45
|
+
source: input.source,
|
|
46
|
+
identity: input.identity,
|
|
47
|
+
host: input.host,
|
|
48
|
+
fingerprint: input.fingerprint,
|
|
49
|
+
reason,
|
|
50
|
+
previousFingerprint: existing?.fingerprint,
|
|
51
|
+
});
|
|
52
|
+
if (!approved) {
|
|
53
|
+
if (existing && existing.fingerprint !== input.fingerprint) {
|
|
54
|
+
throw new Error(`Source fingerprint changed for ${input.source} (${existing.fingerprint} -> ${input.fingerprint}). Update blocked until re-approved.`);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Source ${input.source} is not trusted. Re-run with explicit trust approval.`);
|
|
57
|
+
}
|
|
58
|
+
const actorScope = input.allowOverride ? "non-interactive-override" : "user";
|
|
59
|
+
this.ledger.upsert({
|
|
60
|
+
key,
|
|
61
|
+
sourceType: input.sourceType,
|
|
62
|
+
source: input.source,
|
|
63
|
+
identity: input.identity,
|
|
64
|
+
host: input.host.toLowerCase(),
|
|
65
|
+
fingerprint: input.fingerprint,
|
|
66
|
+
approvedAt: new Date().toISOString(),
|
|
67
|
+
actorScope,
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
approved: true,
|
|
71
|
+
reusedApproval: false,
|
|
72
|
+
reason: reason === "new-source" ? "new-source-approved" : "fingerprint-reapproved",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async requestConsent(request) {
|
|
76
|
+
if (!this.consentProvider) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return this.consentProvider(request);
|
|
80
|
+
}
|
|
81
|
+
buildKey(sourceType, identity) {
|
|
82
|
+
return `${sourceType}:${identity}`;
|
|
83
|
+
}
|
|
84
|
+
assertHostAllowed(hostRaw) {
|
|
85
|
+
const host = hostRaw.trim().toLowerCase();
|
|
86
|
+
if (!host) {
|
|
87
|
+
throw new Error("Security policy rejected source with empty host.");
|
|
88
|
+
}
|
|
89
|
+
if (!this.allowedHosts.has(host)) {
|
|
90
|
+
throw new Error(`Security policy rejected source host "${host}" (not in allowlist).`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=source-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-security.js","sourceRoot":"","sources":["../../../src/core/security/source-security.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TrustActorScope, TrustSourceType } from \"./trust-ledger.js\";\nimport { TrustLedger } from \"./trust-ledger.js\";\n\nconst DEFAULT_ALLOWED_HOSTS = [\n\t\"registry.npmjs.org\",\n\t\"npmjs.org\",\n\t\"github.com\",\n\t\"gitlab.com\",\n\t\"bitbucket.org\",\n\t\"api.github.com\",\n\t\"raw.githubusercontent.com\",\n];\n\nexport interface SecurityConsentRequest {\n\taction: \"install\" | \"update\" | \"download\";\n\tsourceType: TrustSourceType;\n\tsource: string;\n\tidentity: string;\n\thost: string;\n\tfingerprint: string;\n\treason: \"new-source\" | \"fingerprint-change\";\n\tpreviousFingerprint?: string;\n}\n\nexport interface SourceVerificationInput {\n\taction: \"install\" | \"update\" | \"download\";\n\tsourceType: TrustSourceType;\n\tsource: string;\n\tidentity: string;\n\thost: string;\n\tfingerprint: string;\n\tallowOverride?: boolean;\n\tallowPrompt?: boolean;\n}\n\nexport interface SourceVerificationResult {\n\tapproved: boolean;\n\treusedApproval: boolean;\n\treason: \"already-trusted\" | \"new-source-approved\" | \"fingerprint-reapproved\";\n}\n\nexport class SourceSecurityManager {\n\tprivate readonly ledger: TrustLedger;\n\tprivate readonly allowedHosts: Set<string>;\n\tprivate readonly consentProvider?: (request: SecurityConsentRequest) => Promise<boolean>;\n\n\tconstructor(options: {\n\t\tagentDir: string;\n\t\tallowedHosts?: string[];\n\t\tconsentProvider?: (request: SecurityConsentRequest) => Promise<boolean>;\n\t}) {\n\t\tthis.ledger = new TrustLedger(options.agentDir);\n\t\tconst normalizedHosts = (options.allowedHosts ?? DEFAULT_ALLOWED_HOSTS)\n\t\t\t.map((host) => host.trim().toLowerCase())\n\t\t\t.filter((host) => host.length > 0);\n\t\tthis.allowedHosts = new Set(normalizedHosts);\n\t\tthis.consentProvider = options.consentProvider;\n\t}\n\n\tgetLedgerPath(): string {\n\t\treturn this.ledger.getPath();\n\t}\n\n\tgetAllowedHosts(): string[] {\n\t\treturn [...this.allowedHosts];\n\t}\n\n\tasync verify(input: SourceVerificationInput): Promise<SourceVerificationResult> {\n\t\tthis.assertHostAllowed(input.host);\n\t\tconst key = this.buildKey(input.sourceType, input.identity);\n\t\tconst existing = this.ledger.get(key);\n\t\tif (existing && existing.fingerprint === input.fingerprint) {\n\t\t\treturn {\n\t\t\t\tapproved: true,\n\t\t\t\treusedApproval: true,\n\t\t\t\treason: \"already-trusted\",\n\t\t\t};\n\t\t}\n\n\t\tconst reason: SecurityConsentRequest[\"reason\"] = existing ? \"fingerprint-change\" : \"new-source\";\n\t\tconst approved =\n\t\t\tinput.allowOverride === true\n\t\t\t\t? true\n\t\t\t\t: input.allowPrompt === false\n\t\t\t\t\t? false\n\t\t\t\t\t: await this.requestConsent({\n\t\t\t\t\t\taction: input.action,\n\t\t\t\t\t\tsourceType: input.sourceType,\n\t\t\t\t\t\tsource: input.source,\n\t\t\t\t\t\tidentity: input.identity,\n\t\t\t\t\t\thost: input.host,\n\t\t\t\t\t\tfingerprint: input.fingerprint,\n\t\t\t\t\t\treason,\n\t\t\t\t\t\tpreviousFingerprint: existing?.fingerprint,\n\t\t\t\t\t});\n\t\tif (!approved) {\n\t\t\tif (existing && existing.fingerprint !== input.fingerprint) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Source fingerprint changed for ${input.source} (${existing.fingerprint} -> ${input.fingerprint}). Update blocked until re-approved.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(`Source ${input.source} is not trusted. Re-run with explicit trust approval.`);\n\t\t}\n\n\t\tconst actorScope: TrustActorScope = input.allowOverride ? \"non-interactive-override\" : \"user\";\n\t\tthis.ledger.upsert({\n\t\t\tkey,\n\t\t\tsourceType: input.sourceType,\n\t\t\tsource: input.source,\n\t\t\tidentity: input.identity,\n\t\t\thost: input.host.toLowerCase(),\n\t\t\tfingerprint: input.fingerprint,\n\t\t\tapprovedAt: new Date().toISOString(),\n\t\t\tactorScope,\n\t\t});\n\n\t\treturn {\n\t\t\tapproved: true,\n\t\t\treusedApproval: false,\n\t\t\treason: reason === \"new-source\" ? \"new-source-approved\" : \"fingerprint-reapproved\",\n\t\t};\n\t}\n\n\tprivate async requestConsent(request: SecurityConsentRequest): Promise<boolean> {\n\t\tif (!this.consentProvider) {\n\t\t\treturn false;\n\t\t}\n\t\treturn this.consentProvider(request);\n\t}\n\n\tprivate buildKey(sourceType: TrustSourceType, identity: string): string {\n\t\treturn `${sourceType}:${identity}`;\n\t}\n\n\tprivate assertHostAllowed(hostRaw: string): void {\n\t\tconst host = hostRaw.trim().toLowerCase();\n\t\tif (!host) {\n\t\t\tthrow new Error(\"Security policy rejected source with empty host.\");\n\t\t}\n\t\tif (!this.allowedHosts.has(host)) {\n\t\t\tthrow new Error(`Security policy rejected source host \"${host}\" (not in allowlist).`);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type TrustSourceType = "npm" | "git" | "tool-download";
|
|
2
|
+
export type TrustActorScope = "user" | "workspace" | "session" | "non-interactive-override";
|
|
3
|
+
export interface TrustLedgerEntry {
|
|
4
|
+
key: string;
|
|
5
|
+
sourceType: TrustSourceType;
|
|
6
|
+
source: string;
|
|
7
|
+
identity: string;
|
|
8
|
+
host: string;
|
|
9
|
+
fingerprint: string;
|
|
10
|
+
approvedAt: string;
|
|
11
|
+
actorScope: TrustActorScope;
|
|
12
|
+
}
|
|
13
|
+
export declare class TrustLedger {
|
|
14
|
+
private readonly path;
|
|
15
|
+
private data;
|
|
16
|
+
constructor(agentDir: string);
|
|
17
|
+
getPath(): string;
|
|
18
|
+
get(key: string): TrustLedgerEntry | undefined;
|
|
19
|
+
getAll(): TrustLedgerEntry[];
|
|
20
|
+
upsert(entry: TrustLedgerEntry): void;
|
|
21
|
+
private load;
|
|
22
|
+
private save;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=trust-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-ledger.d.ts","sourceRoot":"","sources":["../../../src/core/security/trust-ledger.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,eAAe,CAAC;AAC9D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,0BAA0B,CAAC;AAE5F,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,eAAe,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,eAAe,CAAC;CAC5B;AASD,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,IAAI,CAAkB;gBAElB,QAAQ,EAAE,MAAM;IAK5B,OAAO,IAAI,MAAM;IAIjB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI9C,MAAM,IAAI,gBAAgB,EAAE;IAI5B,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAUrC,OAAO,CAAC,IAAI;IAgCZ,OAAO,CAAC,IAAI;CAOZ"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
const TRUST_LEDGER_FILENAME = "trust-ledger.json";
|
|
4
|
+
export class TrustLedger {
|
|
5
|
+
constructor(agentDir) {
|
|
6
|
+
this.path = join(agentDir, TRUST_LEDGER_FILENAME);
|
|
7
|
+
this.data = this.load();
|
|
8
|
+
}
|
|
9
|
+
getPath() {
|
|
10
|
+
return this.path;
|
|
11
|
+
}
|
|
12
|
+
get(key) {
|
|
13
|
+
return this.data.entries.find((entry) => entry.key === key);
|
|
14
|
+
}
|
|
15
|
+
getAll() {
|
|
16
|
+
return this.data.entries.map((entry) => ({ ...entry }));
|
|
17
|
+
}
|
|
18
|
+
upsert(entry) {
|
|
19
|
+
const index = this.data.entries.findIndex((item) => item.key === entry.key);
|
|
20
|
+
if (index >= 0) {
|
|
21
|
+
this.data.entries[index] = { ...entry };
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.data.entries.push({ ...entry });
|
|
25
|
+
}
|
|
26
|
+
this.save();
|
|
27
|
+
}
|
|
28
|
+
load() {
|
|
29
|
+
if (!existsSync(this.path)) {
|
|
30
|
+
return { version: 1, entries: [] };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync(this.path, "utf8");
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
36
|
+
return {
|
|
37
|
+
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
38
|
+
entries: entries
|
|
39
|
+
.filter((entry) => {
|
|
40
|
+
return (typeof entry === "object" &&
|
|
41
|
+
entry !== null &&
|
|
42
|
+
typeof entry.key === "string" &&
|
|
43
|
+
typeof entry.sourceType === "string" &&
|
|
44
|
+
typeof entry.source === "string" &&
|
|
45
|
+
typeof entry.identity === "string" &&
|
|
46
|
+
typeof entry.host === "string" &&
|
|
47
|
+
typeof entry.fingerprint === "string" &&
|
|
48
|
+
typeof entry.approvedAt === "string" &&
|
|
49
|
+
typeof entry.actorScope === "string");
|
|
50
|
+
})
|
|
51
|
+
.map((entry) => ({ ...entry })),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { version: 1, entries: [] };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
save() {
|
|
59
|
+
const dir = dirname(this.path);
|
|
60
|
+
if (!existsSync(dir)) {
|
|
61
|
+
mkdirSync(dir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
writeFileSync(this.path, `${JSON.stringify(this.data, null, 2)}\n`, "utf8");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=trust-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-ledger.js","sourceRoot":"","sources":["../../../src/core/security/trust-ledger.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport type TrustSourceType = \"npm\" | \"git\" | \"tool-download\";\nexport type TrustActorScope = \"user\" | \"workspace\" | \"session\" | \"non-interactive-override\";\n\nexport interface TrustLedgerEntry {\n\tkey: string;\n\tsourceType: TrustSourceType;\n\tsource: string;\n\tidentity: string;\n\thost: string;\n\tfingerprint: string;\n\tapprovedAt: string;\n\tactorScope: TrustActorScope;\n}\n\ninterface TrustLedgerFile {\n\tversion: number;\n\tentries: TrustLedgerEntry[];\n}\n\nconst TRUST_LEDGER_FILENAME = \"trust-ledger.json\";\n\nexport class TrustLedger {\n\tprivate readonly path: string;\n\tprivate data: TrustLedgerFile;\n\n\tconstructor(agentDir: string) {\n\t\tthis.path = join(agentDir, TRUST_LEDGER_FILENAME);\n\t\tthis.data = this.load();\n\t}\n\n\tgetPath(): string {\n\t\treturn this.path;\n\t}\n\n\tget(key: string): TrustLedgerEntry | undefined {\n\t\treturn this.data.entries.find((entry) => entry.key === key);\n\t}\n\n\tgetAll(): TrustLedgerEntry[] {\n\t\treturn this.data.entries.map((entry) => ({ ...entry }));\n\t}\n\n\tupsert(entry: TrustLedgerEntry): void {\n\t\tconst index = this.data.entries.findIndex((item) => item.key === entry.key);\n\t\tif (index >= 0) {\n\t\t\tthis.data.entries[index] = { ...entry };\n\t\t} else {\n\t\t\tthis.data.entries.push({ ...entry });\n\t\t}\n\t\tthis.save();\n\t}\n\n\tprivate load(): TrustLedgerFile {\n\t\tif (!existsSync(this.path)) {\n\t\t\treturn { version: 1, entries: [] };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = readFileSync(this.path, \"utf8\");\n\t\t\tconst parsed = JSON.parse(raw) as Partial<TrustLedgerFile>;\n\t\t\tconst entries = Array.isArray(parsed.entries) ? parsed.entries : [];\n\t\t\treturn {\n\t\t\t\tversion: typeof parsed.version === \"number\" ? parsed.version : 1,\n\t\t\t\tentries: entries\n\t\t\t\t\t.filter((entry): entry is TrustLedgerEntry => {\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\ttypeof entry === \"object\" &&\n\t\t\t\t\t\t\tentry !== null &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).key === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).sourceType === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).source === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).identity === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).host === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).fingerprint === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).approvedAt === \"string\" &&\n\t\t\t\t\t\t\ttypeof (entry as TrustLedgerEntry).actorScope === \"string\"\n\t\t\t\t\t\t);\n\t\t\t\t\t})\n\t\t\t\t\t.map((entry) => ({ ...entry })),\n\t\t\t};\n\t\t} catch {\n\t\t\treturn { version: 1, entries: [] };\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\tconst dir = dirname(this.path);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\t\twriteFileSync(this.path, `${JSON.stringify(this.data, null, 2)}\\n`, \"utf8\");\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiB9E,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAIlB,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,uBAAuB,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, statSync, writeFileSync, } from "fs";
|
|
3
3
|
import { readdir, readFile, stat } from "fs/promises";
|
|
4
|
-
import { join, resolve } from "path";
|
|
4
|
+
import { dirname, join, resolve } from "path";
|
|
5
5
|
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
6
6
|
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "./messages.js";
|
|
7
7
|
export const CURRENT_SESSION_VERSION = 3;
|
|
8
|
+
const SESSION_INDEX_FILENAME = "session-index.jsonl";
|
|
9
|
+
const SESSION_INDEX_MAX_ALL_MESSAGES_CHARS = 40_000;
|
|
8
10
|
/** Generate a unique short ID (8 hex chars, collision-checked) */
|
|
9
11
|
function generateId(byId) {
|
|
10
12
|
for (let i = 0; i < 100; i++) {
|
|
@@ -386,6 +388,94 @@ async function buildSessionInfo(filePath) {
|
|
|
386
388
|
return null;
|
|
387
389
|
}
|
|
388
390
|
}
|
|
391
|
+
function getSessionIndexPath() {
|
|
392
|
+
return join(getSessionsDir(), SESSION_INDEX_FILENAME);
|
|
393
|
+
}
|
|
394
|
+
function toSessionIndexRecord(info) {
|
|
395
|
+
const allMessagesText = info.allMessagesText.length > SESSION_INDEX_MAX_ALL_MESSAGES_CHARS
|
|
396
|
+
? info.allMessagesText.slice(0, SESSION_INDEX_MAX_ALL_MESSAGES_CHARS)
|
|
397
|
+
: info.allMessagesText;
|
|
398
|
+
return {
|
|
399
|
+
path: info.path,
|
|
400
|
+
id: info.id,
|
|
401
|
+
cwd: info.cwd,
|
|
402
|
+
name: info.name,
|
|
403
|
+
parentSessionPath: info.parentSessionPath,
|
|
404
|
+
created: info.created.toISOString(),
|
|
405
|
+
modified: info.modified.toISOString(),
|
|
406
|
+
messageCount: info.messageCount,
|
|
407
|
+
firstMessage: info.firstMessage,
|
|
408
|
+
allMessagesText,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function parseSessionIndexRecord(raw) {
|
|
412
|
+
if (!raw || typeof raw !== "object")
|
|
413
|
+
return null;
|
|
414
|
+
const record = raw;
|
|
415
|
+
if (typeof record.path !== "string" ||
|
|
416
|
+
typeof record.id !== "string" ||
|
|
417
|
+
typeof record.cwd !== "string" ||
|
|
418
|
+
typeof record.created !== "string" ||
|
|
419
|
+
typeof record.modified !== "string" ||
|
|
420
|
+
typeof record.messageCount !== "number" ||
|
|
421
|
+
typeof record.firstMessage !== "string" ||
|
|
422
|
+
typeof record.allMessagesText !== "string") {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const created = new Date(record.created);
|
|
426
|
+
const modified = new Date(record.modified);
|
|
427
|
+
if (Number.isNaN(created.getTime()) || Number.isNaN(modified.getTime())) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
path: record.path,
|
|
432
|
+
id: record.id,
|
|
433
|
+
cwd: record.cwd,
|
|
434
|
+
name: typeof record.name === "string" ? record.name : undefined,
|
|
435
|
+
parentSessionPath: typeof record.parentSessionPath === "string" ? record.parentSessionPath : undefined,
|
|
436
|
+
created,
|
|
437
|
+
modified,
|
|
438
|
+
messageCount: Math.max(0, Math.trunc(record.messageCount)),
|
|
439
|
+
firstMessage: record.firstMessage || "(no messages)",
|
|
440
|
+
allMessagesText: record.allMessagesText,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function loadSessionIndex(indexPath) {
|
|
444
|
+
if (!existsSync(indexPath)) {
|
|
445
|
+
return { records: new Map(), loaded: false };
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
const raw = readFileSync(indexPath, "utf8");
|
|
449
|
+
const records = new Map();
|
|
450
|
+
for (const line of raw.split("\n")) {
|
|
451
|
+
if (!line.trim())
|
|
452
|
+
continue;
|
|
453
|
+
try {
|
|
454
|
+
const parsed = JSON.parse(line);
|
|
455
|
+
const info = parseSessionIndexRecord(parsed);
|
|
456
|
+
if (!info)
|
|
457
|
+
continue;
|
|
458
|
+
records.set(info.path, info);
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// Skip malformed index lines and continue.
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return { records, loaded: true };
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return { records: new Map(), loaded: false };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function writeSessionIndex(indexPath, sessions) {
|
|
471
|
+
const dir = dirname(indexPath);
|
|
472
|
+
if (!existsSync(dir)) {
|
|
473
|
+
mkdirSync(dir, { recursive: true });
|
|
474
|
+
}
|
|
475
|
+
const lines = sessions.map((session) => JSON.stringify(toSessionIndexRecord(session)));
|
|
476
|
+
const content = lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
477
|
+
writeFileSync(indexPath, content, "utf8");
|
|
478
|
+
}
|
|
389
479
|
async function listSessionsFromDir(dir, onProgress, progressOffset = 0, progressTotal) {
|
|
390
480
|
const sessions = [];
|
|
391
481
|
if (!existsSync(dir)) {
|
|
@@ -1055,34 +1145,57 @@ export class SessionManager {
|
|
|
1055
1145
|
}
|
|
1056
1146
|
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
1057
1147
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => join(sessionsDir, e.name));
|
|
1058
|
-
|
|
1059
|
-
let totalFiles = 0;
|
|
1060
|
-
const dirFiles = [];
|
|
1148
|
+
const allFiles = [];
|
|
1061
1149
|
for (const dir of dirs) {
|
|
1062
1150
|
try {
|
|
1063
1151
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl"));
|
|
1064
|
-
|
|
1065
|
-
|
|
1152
|
+
for (const file of files) {
|
|
1153
|
+
allFiles.push(join(dir, file));
|
|
1154
|
+
}
|
|
1066
1155
|
}
|
|
1067
1156
|
catch {
|
|
1068
|
-
|
|
1157
|
+
// Skip unreadable directory.
|
|
1069
1158
|
}
|
|
1070
1159
|
}
|
|
1071
|
-
|
|
1160
|
+
const totalFiles = allFiles.length;
|
|
1161
|
+
const indexPath = getSessionIndexPath();
|
|
1162
|
+
const index = loadSessionIndex(indexPath);
|
|
1072
1163
|
let loaded = 0;
|
|
1073
|
-
const sessions = [];
|
|
1074
|
-
const allFiles = dirFiles.flat();
|
|
1075
1164
|
const results = await Promise.all(allFiles.map(async (file) => {
|
|
1076
|
-
|
|
1165
|
+
let refreshed = false;
|
|
1166
|
+
let info = null;
|
|
1167
|
+
const indexed = index.records.get(file);
|
|
1168
|
+
if (indexed) {
|
|
1169
|
+
try {
|
|
1170
|
+
const fileStats = await stat(file);
|
|
1171
|
+
if (fileStats.mtime.getTime() <= indexed.modified.getTime()) {
|
|
1172
|
+
info = indexed;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
// File disappeared or became unreadable; ignore indexed entry.
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (!info) {
|
|
1180
|
+
refreshed = true;
|
|
1181
|
+
info = await buildSessionInfo(file);
|
|
1182
|
+
}
|
|
1077
1183
|
loaded++;
|
|
1078
1184
|
onProgress?.(loaded, totalFiles);
|
|
1079
|
-
return info;
|
|
1185
|
+
return { info, refreshed };
|
|
1080
1186
|
}));
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1187
|
+
const sessions = [];
|
|
1188
|
+
let shouldRewriteIndex = !index.loaded || index.records.size !== totalFiles;
|
|
1189
|
+
for (const result of results) {
|
|
1190
|
+
if (result.refreshed)
|
|
1191
|
+
shouldRewriteIndex = true;
|
|
1192
|
+
if (result.info) {
|
|
1193
|
+
sessions.push(result.info);
|
|
1084
1194
|
}
|
|
1085
1195
|
}
|
|
1196
|
+
if (shouldRewriteIndex) {
|
|
1197
|
+
writeSessionIndex(indexPath, sessions);
|
|
1198
|
+
}
|
|
1086
1199
|
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1087
1200
|
return sessions;
|
|
1088
1201
|
}
|