@duckmind/dm-darwin-x64 0.13.2 → 0.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -2
- package/README.md +5 -5
- package/dm +0 -0
- package/docs/settings.md +1 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/extensions/.dm-extensions.json +15 -15
- package/extensions/dm-multicodex/README.md +3 -1
- package/extensions/dm-multicodex/account-manager.test.ts +3 -1
- package/extensions/dm-multicodex/account-manager.ts +27 -1
- package/extensions/dm-multicodex/commands.test.ts +1 -0
- package/extensions/dm-multicodex/commands.ts +81 -1
- package/extensions/dm-multicodex/index.ts +7 -0
- package/extensions/dm-multicodex/node_modules/.package-lock.json +28 -28
- package/extensions/dm-multicodex/package-lock.json +11145 -9633
- package/extensions/dm-multicodex/package.json +56 -56
- package/extensions/dm-multicodex/storage.ts +2 -2
- package/extensions/dm-multicodex/sync.test.ts +114 -0
- package/extensions/dm-multicodex/sync.ts +249 -0
- package/extensions/dm-multicodex/tsconfig.json +17 -0
- package/extensions/dm-subagents/async-execution.ts +2 -0
- package/extensions/dm-subagents/execution.ts +1 -1
- package/extensions/dm-subagents/intercom-bridge.ts +8 -0
- package/extensions/dm-subagents/package.json +1 -1
- package/extensions/dm-subagents/skills.ts +117 -25
- package/extensions/dm-subagents/subagent-executor.ts +2 -6
- package/extensions/dm-subagents/subagent-runner.ts +10 -2
- package/extensions/dm-subagents/worktree.ts +27 -9
- package/extensions/dm-thinking-timer/README.md +1 -1
- package/extensions/dm-ultrathink/src/naming.ts +151 -10
- package/package.json +1 -1
- package/theme/duckmind.json +77 -65
|
@@ -1,58 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
2
|
+
"name": "dm-multicodex",
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "Codex account rotation extension for DM",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/victor-software-house/pi-multicodex.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/victor-software-house/pi-multicodex#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/victor-software-house/pi-multicodex/issues"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"lint": "biome check .",
|
|
17
|
+
"test": "vitest run -c vitest.config.ts",
|
|
18
|
+
"tsgo": "tsgo -p tsconfig.json",
|
|
19
|
+
"generate:schema": "bun scripts/generate-schema.ts",
|
|
20
|
+
"check": "pnpm lint && pnpm tsgo && pnpm test",
|
|
21
|
+
"pack:dry": "npm pack --dry-run",
|
|
22
|
+
"release:dry": "pnpm exec semantic-release --dry-run"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"pi-provider-utils": "^0.0.0",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@mariozechner/pi-ai": "*",
|
|
30
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
31
|
+
"@mariozechner/pi-tui": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "^2.4.7",
|
|
35
|
+
"@commitlint/cli": "^20.4.4",
|
|
36
|
+
"@commitlint/config-conventional": "^20.4.4",
|
|
37
|
+
"@mariozechner/pi-ai": "^0.63.1",
|
|
38
|
+
"@mariozechner/pi-coding-agent": "^0.63.1",
|
|
39
|
+
"@mariozechner/pi-tui": "^0.63.1",
|
|
40
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
41
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
42
|
+
"@semantic-release/git": "^10.0.1",
|
|
43
|
+
"@semantic-release/github": "^12.0.6",
|
|
44
|
+
"@semantic-release/npm": "^13.1.5",
|
|
45
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
46
|
+
"@types/node": "^25.5.0",
|
|
47
|
+
"@typescript/native-preview": "7.0.0-dev.20260314.1",
|
|
48
|
+
"conventional-changelog-conventionalcommits": "^9.3.0",
|
|
49
|
+
"semantic-release": "^25.0.3",
|
|
50
|
+
"typescript": "^5.9.3",
|
|
51
|
+
"vitest": "^4.1.0"
|
|
52
|
+
},
|
|
53
|
+
"pi": {
|
|
54
|
+
"extensions": [
|
|
55
|
+
"./index.ts"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
58
|
}
|
|
@@ -90,7 +90,7 @@ function stripLegacyFields(raw: Record<string, unknown>): boolean {
|
|
|
90
90
|
return stripped;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function
|
|
93
|
+
export function coerceStorageData(raw: unknown): StorageData {
|
|
94
94
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
95
95
|
return { version: CURRENT_VERSION, accounts: [], activeEmail: undefined };
|
|
96
96
|
}
|
|
@@ -141,7 +141,7 @@ export function loadStorage(): StorageData {
|
|
|
141
141
|
!("version" in raw) ||
|
|
142
142
|
raw.version !== CURRENT_VERSION ||
|
|
143
143
|
needsLegacyStrip(raw);
|
|
144
|
-
const data =
|
|
144
|
+
const data = coerceStorageData(raw);
|
|
145
145
|
if (needsMigration) {
|
|
146
146
|
saveStorage(data);
|
|
147
147
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCipheriv,
|
|
3
|
+
createHash,
|
|
4
|
+
pbkdf2Sync,
|
|
5
|
+
randomBytes,
|
|
6
|
+
} from "node:crypto";
|
|
7
|
+
import { describe, expect, it, vi } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
decryptCodexAccountsBundle,
|
|
10
|
+
type EncryptedCodexAccountsBundle,
|
|
11
|
+
syncManagedAccountsFromDuckMind,
|
|
12
|
+
} from "./sync";
|
|
13
|
+
|
|
14
|
+
function createEncryptedBundle(
|
|
15
|
+
secret: string,
|
|
16
|
+
plaintext: Record<string, unknown>,
|
|
17
|
+
): EncryptedCodexAccountsBundle {
|
|
18
|
+
const salt = randomBytes(16);
|
|
19
|
+
const iv = randomBytes(12);
|
|
20
|
+
const iterations = 100_000;
|
|
21
|
+
const serialized = Buffer.from(JSON.stringify(plaintext), "utf8");
|
|
22
|
+
const key = pbkdf2Sync(secret, salt, iterations, 32, "sha256");
|
|
23
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
24
|
+
const ciphertext = Buffer.concat([
|
|
25
|
+
cipher.update(serialized),
|
|
26
|
+
cipher.final(),
|
|
27
|
+
cipher.getAuthTag(),
|
|
28
|
+
]);
|
|
29
|
+
return {
|
|
30
|
+
version: 1,
|
|
31
|
+
algorithm: "AES-256-GCM",
|
|
32
|
+
kdf: {
|
|
33
|
+
name: "PBKDF2",
|
|
34
|
+
hash: "SHA-256",
|
|
35
|
+
iterations,
|
|
36
|
+
salt: salt.toString("base64"),
|
|
37
|
+
},
|
|
38
|
+
iv: iv.toString("base64"),
|
|
39
|
+
ciphertext: ciphertext.toString("base64"),
|
|
40
|
+
file_name: "codex-accounts.json",
|
|
41
|
+
content_type: "application/json",
|
|
42
|
+
plaintext_sha256: createHash("sha256").update(serialized).digest("hex"),
|
|
43
|
+
plaintext_bytes: serialized.length,
|
|
44
|
+
uploaded_at: "2026-04-13T18:00:00.000Z",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("decryptCodexAccountsBundle", () => {
|
|
49
|
+
it("decrypts a DuckMind bundle into normalized storage data", () => {
|
|
50
|
+
const secret = "duckmind-secret";
|
|
51
|
+
const bundle = createEncryptedBundle(secret, {
|
|
52
|
+
accounts: [
|
|
53
|
+
{
|
|
54
|
+
email: "alpha@example.com",
|
|
55
|
+
accessToken: "access-token",
|
|
56
|
+
refreshToken: "refresh-token",
|
|
57
|
+
expiresAt: 1234567890,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
activeEmail: "alpha@example.com",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = decryptCodexAccountsBundle(bundle, secret);
|
|
64
|
+
expect(result.storage.accounts).toHaveLength(1);
|
|
65
|
+
expect(result.storage.accounts[0]?.email).toBe("alpha@example.com");
|
|
66
|
+
expect(result.storage.activeEmail).toBe("alpha@example.com");
|
|
67
|
+
expect(result.uploadedAt).toBe("2026-04-13T18:00:00.000Z");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("rejects an invalid sync secret", () => {
|
|
71
|
+
const bundle = createEncryptedBundle("right-secret", {
|
|
72
|
+
accounts: [],
|
|
73
|
+
activeEmail: undefined,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(() => decryptCodexAccountsBundle(bundle, "wrong-secret")).toThrow(
|
|
77
|
+
/invalid/i,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("syncManagedAccountsFromDuckMind", () => {
|
|
83
|
+
it("downloads the encrypted bundle with browser-like headers", async () => {
|
|
84
|
+
const secret = "duckmind-secret";
|
|
85
|
+
const bundle = createEncryptedBundle(secret, {
|
|
86
|
+
accounts: [],
|
|
87
|
+
activeEmail: undefined,
|
|
88
|
+
});
|
|
89
|
+
const fetchImpl = vi.fn(
|
|
90
|
+
async (_url: string, init?: { headers?: Record<string, string> }) => ({
|
|
91
|
+
ok: true,
|
|
92
|
+
status: 200,
|
|
93
|
+
text: async () => JSON.stringify(bundle),
|
|
94
|
+
init,
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const result = await syncManagedAccountsFromDuckMind(secret, {
|
|
99
|
+
fetchImpl,
|
|
100
|
+
sourceUrl: "https://example.com/codex-accounts.json",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(fetchImpl).toHaveBeenCalledWith(
|
|
104
|
+
"https://example.com/codex-accounts.json",
|
|
105
|
+
expect.objectContaining({
|
|
106
|
+
headers: expect.objectContaining({
|
|
107
|
+
Accept: expect.stringContaining("application/json"),
|
|
108
|
+
"User-Agent": "Mozilla/5.0",
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
expect(result.sourceUrl).toBe("https://example.com/codex-accounts.json");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { createDecipheriv, createHash, pbkdf2Sync } from "node:crypto";
|
|
2
|
+
import { coerceStorageData, type StorageData } from "./storage";
|
|
3
|
+
|
|
4
|
+
export const DUCKMIND_CODEX_ACCOUNTS_URL =
|
|
5
|
+
"https://duckmind.ai/codex-accounts.json";
|
|
6
|
+
|
|
7
|
+
type EncryptedKdfConfig = {
|
|
8
|
+
name: string;
|
|
9
|
+
hash: string;
|
|
10
|
+
iterations: number;
|
|
11
|
+
salt: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type EncryptedCodexAccountsBundle = {
|
|
15
|
+
version: number;
|
|
16
|
+
algorithm: string;
|
|
17
|
+
kdf: EncryptedKdfConfig;
|
|
18
|
+
iv: string;
|
|
19
|
+
ciphertext: string;
|
|
20
|
+
file_name?: string;
|
|
21
|
+
content_type?: string;
|
|
22
|
+
plaintext_sha256: string;
|
|
23
|
+
plaintext_bytes?: number;
|
|
24
|
+
uploaded_at?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type FetchResponseLike = {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
status: number;
|
|
30
|
+
statusText?: string;
|
|
31
|
+
text(): Promise<string>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type FetchLike = (
|
|
35
|
+
url: string,
|
|
36
|
+
init?: {
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
},
|
|
39
|
+
) => Promise<FetchResponseLike>;
|
|
40
|
+
|
|
41
|
+
export interface SyncManagedAccountsResult {
|
|
42
|
+
storage: StorageData;
|
|
43
|
+
uploadedAt?: string;
|
|
44
|
+
fileName?: string;
|
|
45
|
+
plaintextSha256: string;
|
|
46
|
+
sourceUrl: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ensureString(value: unknown, label: string): string {
|
|
50
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
51
|
+
throw new Error(`Missing ${label} in DuckMind account bundle.`);
|
|
52
|
+
}
|
|
53
|
+
return value.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensurePositiveInteger(value: unknown, label: string): number {
|
|
57
|
+
if (!Number.isInteger(value) || typeof value !== "number" || value <= 0) {
|
|
58
|
+
throw new Error(`Invalid ${label} in DuckMind account bundle.`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseBundle(raw: unknown): EncryptedCodexAccountsBundle {
|
|
64
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
65
|
+
throw new Error("DuckMind account bundle is not a JSON object.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const record = raw as Record<string, unknown>;
|
|
69
|
+
const kdfRecord = record.kdf;
|
|
70
|
+
if (!kdfRecord || typeof kdfRecord !== "object" || Array.isArray(kdfRecord)) {
|
|
71
|
+
throw new Error("DuckMind account bundle is missing kdf settings.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
version: ensurePositiveInteger(record.version, "version"),
|
|
76
|
+
algorithm: ensureString(record.algorithm, "algorithm"),
|
|
77
|
+
kdf: {
|
|
78
|
+
name: ensureString(
|
|
79
|
+
(kdfRecord as Record<string, unknown>).name,
|
|
80
|
+
"kdf.name",
|
|
81
|
+
),
|
|
82
|
+
hash: ensureString(
|
|
83
|
+
(kdfRecord as Record<string, unknown>).hash,
|
|
84
|
+
"kdf.hash",
|
|
85
|
+
),
|
|
86
|
+
iterations: ensurePositiveInteger(
|
|
87
|
+
(kdfRecord as Record<string, unknown>).iterations,
|
|
88
|
+
"kdf.iterations",
|
|
89
|
+
),
|
|
90
|
+
salt: ensureString(
|
|
91
|
+
(kdfRecord as Record<string, unknown>).salt,
|
|
92
|
+
"kdf.salt",
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
iv: ensureString(record.iv, "iv"),
|
|
96
|
+
ciphertext: ensureString(record.ciphertext, "ciphertext"),
|
|
97
|
+
file_name:
|
|
98
|
+
typeof record.file_name === "string" ? record.file_name : undefined,
|
|
99
|
+
content_type:
|
|
100
|
+
typeof record.content_type === "string" ? record.content_type : undefined,
|
|
101
|
+
plaintext_sha256: ensureString(record.plaintext_sha256, "plaintext_sha256"),
|
|
102
|
+
plaintext_bytes:
|
|
103
|
+
typeof record.plaintext_bytes === "number"
|
|
104
|
+
? record.plaintext_bytes
|
|
105
|
+
: undefined,
|
|
106
|
+
uploaded_at:
|
|
107
|
+
typeof record.uploaded_at === "string" ? record.uploaded_at : undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function decodeBase64(value: string, label: string): Buffer {
|
|
112
|
+
try {
|
|
113
|
+
const decoded = Buffer.from(value, "base64");
|
|
114
|
+
if (decoded.length === 0) {
|
|
115
|
+
throw new Error("empty");
|
|
116
|
+
}
|
|
117
|
+
return decoded;
|
|
118
|
+
} catch {
|
|
119
|
+
throw new Error(`Invalid ${label} encoding in DuckMind account bundle.`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function decryptCodexAccountsBundle(
|
|
124
|
+
bundle: EncryptedCodexAccountsBundle,
|
|
125
|
+
secret: string,
|
|
126
|
+
): SyncManagedAccountsResult {
|
|
127
|
+
const trimmedSecret = secret.trim();
|
|
128
|
+
if (!trimmedSecret) {
|
|
129
|
+
throw new Error("Sync secret is required.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (bundle.algorithm !== "AES-256-GCM") {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Unsupported DuckMind bundle algorithm: ${bundle.algorithm}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (bundle.kdf.name !== "PBKDF2") {
|
|
138
|
+
throw new Error(`Unsupported DuckMind bundle KDF: ${bundle.kdf.name}`);
|
|
139
|
+
}
|
|
140
|
+
if (bundle.kdf.hash !== "SHA-256") {
|
|
141
|
+
throw new Error(`Unsupported DuckMind bundle hash: ${bundle.kdf.hash}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const salt = decodeBase64(bundle.kdf.salt, "kdf.salt");
|
|
145
|
+
const iv = decodeBase64(bundle.iv, "iv");
|
|
146
|
+
const encrypted = decodeBase64(bundle.ciphertext, "ciphertext");
|
|
147
|
+
if (iv.length !== 12) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Unsupported DuckMind IV length: expected 12 bytes, got ${iv.length}.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (encrypted.length <= 16) {
|
|
153
|
+
throw new Error("DuckMind ciphertext is too short to contain a GCM tag.");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const key = pbkdf2Sync(
|
|
157
|
+
trimmedSecret,
|
|
158
|
+
salt,
|
|
159
|
+
bundle.kdf.iterations,
|
|
160
|
+
32,
|
|
161
|
+
"sha256",
|
|
162
|
+
);
|
|
163
|
+
const authTag = encrypted.subarray(encrypted.length - 16);
|
|
164
|
+
const payload = encrypted.subarray(0, encrypted.length - 16);
|
|
165
|
+
|
|
166
|
+
let plaintext: Buffer;
|
|
167
|
+
try {
|
|
168
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
169
|
+
decipher.setAuthTag(authTag);
|
|
170
|
+
plaintext = Buffer.concat([decipher.update(payload), decipher.final()]);
|
|
171
|
+
} catch {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"DuckMind sync secret is invalid or the bundle is corrupted.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
typeof bundle.plaintext_bytes === "number" &&
|
|
179
|
+
plaintext.length !== bundle.plaintext_bytes
|
|
180
|
+
) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
"DuckMind account bundle size check failed after decryption.",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const plaintextSha256 = createHash("sha256").update(plaintext).digest("hex");
|
|
187
|
+
if (plaintextSha256 !== bundle.plaintext_sha256) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"DuckMind account bundle integrity check failed after decryption.",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let parsed: unknown;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(plaintext.toString("utf8"));
|
|
196
|
+
} catch {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"DuckMind account bundle decrypted successfully, but the plaintext is not valid JSON.",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
storage: coerceStorageData(parsed),
|
|
204
|
+
uploadedAt: bundle.uploaded_at,
|
|
205
|
+
fileName: bundle.file_name,
|
|
206
|
+
plaintextSha256,
|
|
207
|
+
sourceUrl: DUCKMIND_CODEX_ACCOUNTS_URL,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function syncManagedAccountsFromDuckMind(
|
|
212
|
+
secret: string,
|
|
213
|
+
options?: {
|
|
214
|
+
fetchImpl?: FetchLike;
|
|
215
|
+
sourceUrl?: string;
|
|
216
|
+
},
|
|
217
|
+
): Promise<SyncManagedAccountsResult> {
|
|
218
|
+
const fetchImpl =
|
|
219
|
+
options?.fetchImpl ?? (globalThis.fetch as FetchLike | undefined);
|
|
220
|
+
if (!fetchImpl) {
|
|
221
|
+
throw new Error("Runtime fetch API is unavailable for MultiCodex sync.");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const sourceUrl = options?.sourceUrl ?? DUCKMIND_CODEX_ACCOUNTS_URL;
|
|
225
|
+
const response = await fetchImpl(sourceUrl, {
|
|
226
|
+
headers: {
|
|
227
|
+
Accept: "application/json, text/plain, */*",
|
|
228
|
+
"User-Agent": "Mozilla/5.0",
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`DuckMind account sync failed with HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}.`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let parsed: unknown;
|
|
238
|
+
try {
|
|
239
|
+
parsed = JSON.parse(await response.text());
|
|
240
|
+
} catch {
|
|
241
|
+
throw new Error("DuckMind account sync returned malformed JSON.");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const decrypted = decryptCodexAccountsBundle(parseBundle(parsed), secret);
|
|
245
|
+
return {
|
|
246
|
+
...decrypted,
|
|
247
|
+
sourceUrl,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"types": ["node"],
|
|
13
|
+
"noEmit": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["*.ts"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
@@ -264,6 +264,7 @@ export function executeAsyncChain(
|
|
|
264
264
|
asyncDir,
|
|
265
265
|
sessionId: ctx.currentSessionId,
|
|
266
266
|
piPackageRoot,
|
|
267
|
+
piArgv1: process.argv[1],
|
|
267
268
|
worktreeSetupHook,
|
|
268
269
|
worktreeSetupHookTimeoutMs,
|
|
269
270
|
},
|
|
@@ -384,6 +385,7 @@ export function executeAsyncSingle(
|
|
|
384
385
|
asyncDir,
|
|
385
386
|
sessionId: ctx.currentSessionId,
|
|
386
387
|
piPackageRoot,
|
|
388
|
+
piArgv1: process.argv[1],
|
|
387
389
|
worktreeSetupHook,
|
|
388
390
|
worktreeSetupHookTimeoutMs,
|
|
389
391
|
},
|
|
@@ -155,7 +155,7 @@ async function runSingleAttempt(
|
|
|
155
155
|
finish(-2);
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
-
const unsubscribeIntercomDetach = options.intercomEvents?.on(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
158
|
+
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
159
159
|
if (!options.allowIntercomDetach || detached || processClosed) return;
|
|
160
160
|
if (!payload || typeof payload !== "object") return;
|
|
161
161
|
const requestId = (payload as { requestId?: unknown }).requestId;
|
|
@@ -7,6 +7,7 @@ import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "
|
|
|
7
7
|
const DEFAULT_INTERCOM_EXTENSION_DIR = path.join(os.homedir(), ".dm", "agent", "extensions", "pi-intercom");
|
|
8
8
|
const DEFAULT_INTERCOM_CONFIG_PATH = path.join(os.homedir(), ".dm", "agent", "intercom", "config.json");
|
|
9
9
|
const DEFAULT_SUBAGENT_CONFIG_DIR = path.join(os.homedir(), ".dm", "agent", "extensions", "subagent");
|
|
10
|
+
const DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat";
|
|
10
11
|
const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
11
12
|
const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `Use intercom only for coordination with the orchestrator session:
|
|
12
13
|
- Need a decision or blocked: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
@@ -30,6 +31,13 @@ interface ResolveIntercomBridgeInput {
|
|
|
30
31
|
settingsDir?: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export function resolveIntercomSessionTarget(sessionName: string | undefined, sessionId: string): string {
|
|
35
|
+
const trimmedName = sessionName?.trim();
|
|
36
|
+
if (trimmedName) return trimmedName;
|
|
37
|
+
const normalizedSessionId = sessionId.startsWith("session-") ? sessionId.slice("session-".length) : sessionId;
|
|
38
|
+
return `${DEFAULT_INTERCOM_TARGET_PREFIX}-${normalizedSessionId.slice(0, 8)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
export function resolveIntercomBridgeMode(value: unknown): IntercomBridgeMode {
|
|
34
42
|
if (value === "off" || value === "always" || value === "fork-only") return value;
|
|
35
43
|
return "always";
|