@runeya/runeya 2.0.146 → 2.0.147
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/agent/index.js +10 -4
- package/agent/index.js.map +1 -1
- package/{agent-manager-R7BPQO6D.js → agent-manager-46C4NIQ2.js} +4 -2
- package/{chat-LPML22J5.js → chat-CMNDMXIP.js} +9 -5
- package/{chunk-SEPAURTQ.js → chunk-2NE52CL5.js} +156 -923
- package/chunk-2NE52CL5.js.map +1 -0
- package/chunk-4D3GCBPO.js +462 -0
- package/chunk-4D3GCBPO.js.map +1 -0
- package/{chunk-KV4GJKXU.js → chunk-5XG3X2TM.js} +27 -1479
- package/chunk-5XG3X2TM.js.map +1 -0
- package/chunk-7QXP4FXD.js +969 -0
- package/chunk-7QXP4FXD.js.map +1 -0
- package/chunk-DA2DZBTL.js +43 -0
- package/chunk-DA2DZBTL.js.map +1 -0
- package/chunk-DFPZ4MTI.js +1041 -0
- package/chunk-DFPZ4MTI.js.map +1 -0
- package/{chunk-UD42LACH.js → chunk-JNK6H5KH.js} +164 -1007
- package/chunk-JNK6H5KH.js.map +1 -0
- package/chunk-WDHLEO5S.js +809 -0
- package/chunk-WDHLEO5S.js.map +1 -0
- package/{companion-W3UMZWGH.js → companion-XKMNRIJB.js} +8 -3
- package/environment-resolver-ZDTF3MHI.js +18 -0
- package/environment-resolver-ZDTF3MHI.js.map +1 -0
- package/index.js +7 -7
- package/{install-gate-PVMGJDWE.js → install-gate-OJJEUU4E.js} +2 -2
- package/mcp-ready-TAPBAOZY.js +9 -0
- package/mcp-ready-TAPBAOZY.js.map +1 -0
- package/mcp-server/index.js +24 -0
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/launch.d.ts +6 -0
- package/mcp-server/launch.js +2 -1
- package/mcp-server/launch.js.map +1 -1
- package/package.json +1 -1
- package/project-store-FLXTAD3J.js +23 -0
- package/project-store-FLXTAD3J.js.map +1 -0
- package/{src-LEDGW5KP.js → src-KWO3CKZU.js} +139 -59
- package/src-KWO3CKZU.js.map +1 -0
- package/web/assets/{AgentScalarChatInterface.vue-BYszptaB.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
- package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
- package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
- package/web/assets/AiChatModals-C-hXImkO.js +4 -0
- package/web/assets/AppLayout-At9wk0sx.js +1 -0
- package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
- package/web/assets/AuditPage-BkqbHxjY.js +1 -0
- package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
- package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
- package/web/assets/DashboardPage-dEQop9ql.js +1 -0
- package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
- package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
- package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
- package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
- package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
- package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
- package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
- package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
- package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
- package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
- package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
- package/web/assets/{LaunchShell-DUrSunB4.js → LaunchShell-CDb21ihZ.js} +1 -1
- package/web/assets/LogViewer-DsusKk2b.js +3 -0
- package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
- package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
- package/web/assets/ProcessControls-TEZutuxq.js +1 -0
- package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
- package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
- package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
- package/web/assets/{ProjectLocationGate-DhMVdIjN.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
- package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
- package/web/assets/{RandomJobTestPage-BUS2Ty6-.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
- package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
- package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
- package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
- package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
- package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
- package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
- package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
- package/web/assets/{SettingsPage-4pKUgKPm.css → SettingsPage-NH2fQap4.css} +1 -1
- package/web/assets/SettingsPage-s28BodWe.js +2086 -0
- package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
- package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
- package/web/assets/{VersionBadge-BmArk24p.js → VersionBadge-CW7czacg.js} +1 -1
- package/web/assets/{WikiPage-DADu8WUA.js → WikiPage-BiwpCM3E.js} +1 -1
- package/web/assets/WikiView-CMch_06Z.js +1 -0
- package/web/assets/{add-scalar-classes-dWEWPlFD.js → add-scalar-classes-Ckanjq1h.js} +14 -14
- package/web/assets/{agent.store-CKLAkF9a.js → agent.store-piokQ9Dt.js} +1 -1
- package/web/assets/{auth-Cg9BWTfK.js → auth-BT_jf-5e.js} +2 -2
- package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
- package/web/assets/{cloud-client-D_zCiW0s.js → cloud-client-CzPdZLDq.js} +2 -2
- package/web/assets/{cloud-session-DOaDpZTy.js → cloud-session-BYfn6YM4.js} +2 -2
- package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
- package/web/assets/i18n-D0FT7rCj.js +1 -0
- package/web/assets/index-3LTzgeU-.css +1 -0
- package/web/assets/index-Ci2CNnit.js +309 -0
- package/web/assets/modal-iRaP1izE.js +5 -0
- package/web/assets/notification.store-CvvuTBVz.js +1 -0
- package/web/assets/{project-launch.store-y7JBMNiE.js → project-launch.store-CTmbl8UY.js} +1 -1
- package/web/assets/{project.store-DGJr0Qr9.js → project.store-CNC6_4r4.js} +1 -1
- package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
- package/web/assets/scenario.store-BIFQPXgl.js +7 -0
- package/web/assets/{service.store-5jWsJxPx.js → service.store-B40U4QzI.js} +1 -1
- package/web/assets/settings-scope-CyWMCjRY.js +1 -0
- package/web/assets/settings.store-Dw6bAoWE.js +1 -0
- package/web/assets/{src-Dnwk6NpV.js → src-CrNi4Pjg.js} +3 -3
- package/web/assets/src-DIBHat7I.js +3 -0
- package/web/assets/src-DtCW_XCh.js +1 -0
- package/web/assets/{trpc-Drabzwpd.js → trpc-BAZGRZBD.js} +3 -3
- package/web/assets/{useCloudOrg-BAQEtFPl.js → useCloudOrg-EnXFBwt1.js} +1 -1
- package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
- package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
- package/web/assets/{useProjectCloudOrg-CEN9-_OA.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
- package/web/assets/{useServiceUrlHost-BuoPEXZX.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
- package/web/assets/vue-i18n-o7yraQpR.js +3 -0
- package/web/index.html +21 -21
- package/chunk-KV4GJKXU.js.map +0 -1
- package/chunk-SEPAURTQ.js.map +0 -1
- package/chunk-UD42LACH.js.map +0 -1
- package/src-LEDGW5KP.js.map +0 -1
- package/web/assets/AgentTerminalPage-C3TJ1NL9.js +0 -1
- package/web/assets/AiChatModals-D3scRE3S.js +0 -4
- package/web/assets/AiChatModals-EfRuvId7.css +0 -1
- package/web/assets/AppLayout-BMQUjPKe.js +0 -1
- package/web/assets/AuditPage-CKcExBws.js +0 -1
- package/web/assets/CloudAccountPage-B6oPg0BZ.js +0 -1
- package/web/assets/CloudMessagingPage-C5fwVe-F.js +0 -2
- package/web/assets/DashboardPage-9RV7sx3e.js +0 -1
- package/web/assets/DirectoryPickerDialog-C06BXyfJ.js +0 -1
- package/web/assets/EnvironmentForm-CAvDoira.js +0 -1
- package/web/assets/EnvironmentFormPage-BmAxhN-m.js +0 -1
- package/web/assets/EnvironmentListPage-B8uATPBI.js +0 -1
- package/web/assets/KanbanBoardPage-CQsOkRUf.js +0 -4
- package/web/assets/KanbanListPage-CWZqkjD6.js +0 -1
- package/web/assets/LaunchProjectPage-saSmC7Pr.js +0 -1
- package/web/assets/LaunchServicesPage-BkKyLMEz.js +0 -1
- package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
- package/web/assets/LogViewer-CZBYkH9J.js +0 -3
- package/web/assets/OrgKeyPrompt-DzQ7pwbs.js +0 -1
- package/web/assets/ProcessControls-BEeoNlNV.js +0 -1
- package/web/assets/ProcessControls-BlpnVylq.css +0 -1
- package/web/assets/ProcessDetailPage-CQgu5uKm.js +0 -1
- package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
- package/web/assets/ProjectCreateDialog-BolrhRD-.js +0 -1
- package/web/assets/ProjectPublishDialog-DsZNWrfz.js +0 -1
- package/web/assets/ScenarioEditorPage-MIAGh2us.js +0 -14
- package/web/assets/SendToChatDialog-BxWsyv29.js +0 -1
- package/web/assets/ServiceControlsBar-DqDPAZt7.js +0 -1
- package/web/assets/ServiceDetailPage-hkSAzZ-l.js +0 -2
- package/web/assets/ServiceFormPage-DXqhqXg8.js +0 -1
- package/web/assets/SettingsPage-DS2YhMIM.js +0 -2086
- package/web/assets/UnlockPage-s8SgVcKk.js +0 -1
- package/web/assets/VaultMigrationPage-s0b44MDM.js +0 -1
- package/web/assets/WikiView-DtGwIt_Q.js +0 -1
- package/web/assets/cloud-chat.store-b-BHGz7I.js +0 -1
- package/web/assets/editor.store-DCAZSeVG.js +0 -1
- package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
- package/web/assets/i18n-D0afQEGs.js +0 -1
- package/web/assets/index-D8VRbyoP.js +0 -309
- package/web/assets/index-DsezNbSM.css +0 -1
- package/web/assets/modal-qYgTMkYb.js +0 -5
- package/web/assets/scenario.store-YWHL2_wu.js +0 -7
- package/web/assets/settings-scope-Bv7QGPrt.js +0 -1
- package/web/assets/settings.store-bEsB7oKb.js +0 -1
- package/web/assets/src--GO-VSJ0.js +0 -5
- package/web/assets/src-C-D_y7ou.js +0 -1
- package/web/assets/useEnvVarAutocomplete-BqGTODUv.js +0 -1
- package/web/assets/useProjectCatalog-yocjIKt6.js +0 -1
- package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
- package/web/assets/vue-draggable-plus-DX4rTBiu.js +0 -1
- /package/{agent-manager-R7BPQO6D.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
- /package/{chat-LPML22J5.js.map → chat-CMNDMXIP.js.map} +0 -0
- /package/{companion-W3UMZWGH.js.map → companion-XKMNRIJB.js.map} +0 -0
- /package/{install-gate-PVMGJDWE.js.map → install-gate-OJJEUU4E.js.map} +0 -0
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
projectPathStore,
|
|
3
|
+
projectStore
|
|
4
|
+
} from "./chunk-7QXP4FXD.js";
|
|
5
|
+
import {
|
|
6
|
+
agentManager
|
|
7
|
+
} from "./chunk-5XG3X2TM.js";
|
|
8
|
+
import {
|
|
9
|
+
serviceStore
|
|
10
|
+
} from "./chunk-4D3GCBPO.js";
|
|
11
|
+
import {
|
|
3
12
|
StoreDiskCoordinator,
|
|
4
|
-
agentManager,
|
|
5
13
|
decryptValue,
|
|
6
14
|
encryptValue,
|
|
7
15
|
ensureProjectsLoaded,
|
|
8
|
-
environmentStore,
|
|
9
|
-
forgetProject,
|
|
10
16
|
getEncryptionKey,
|
|
11
|
-
projectKey,
|
|
12
|
-
recordDetached,
|
|
13
17
|
refOfProjectAmong,
|
|
14
|
-
registerProjectLoader,
|
|
15
18
|
rootOfProjectAmong,
|
|
16
|
-
rootOfProjectRef
|
|
17
|
-
|
|
18
|
-
setProjectServices,
|
|
19
|
-
setProjectVault
|
|
20
|
-
} from "./chunk-KV4GJKXU.js";
|
|
19
|
+
rootOfProjectRef
|
|
20
|
+
} from "./chunk-DFPZ4MTI.js";
|
|
21
21
|
import {
|
|
22
22
|
atomicWriteSecure,
|
|
23
23
|
fingerprintFiles,
|
|
@@ -34,977 +34,44 @@ import {
|
|
|
34
34
|
} from "./chunk-MD3VW4HQ.js";
|
|
35
35
|
import {
|
|
36
36
|
allVaultRoots,
|
|
37
|
-
collidesWithMachineRoot,
|
|
38
37
|
env,
|
|
39
|
-
forgetLocalProjectRoot,
|
|
40
38
|
keyedFilePath,
|
|
41
39
|
legacyMachineFilePath,
|
|
42
|
-
listCloudVaults,
|
|
43
|
-
localProjectVaultRoot,
|
|
44
|
-
localVaultCollidesWithMachineRoot,
|
|
45
40
|
localVaultRoot,
|
|
46
|
-
machineFilePath,
|
|
47
|
-
machineRoot,
|
|
48
41
|
orgIdOfRoot,
|
|
49
|
-
readLocalProjectDirs,
|
|
50
|
-
registerLocalProjectDir,
|
|
51
|
-
setLocalProjectRoot,
|
|
52
|
-
unregisterLocalProjectDir,
|
|
53
42
|
vaultFor,
|
|
54
43
|
vaultIdOfRoot
|
|
55
44
|
} from "./chunk-VPSQJOSN.js";
|
|
56
45
|
import {
|
|
57
46
|
COMPANION_RELAY_CLOSE,
|
|
58
47
|
COMPANION_RELAY_OPEN,
|
|
59
|
-
ProjectSchema,
|
|
60
48
|
extractCompanionRelay
|
|
61
49
|
} from "./chunk-MZSDTA4A.js";
|
|
62
50
|
|
|
63
51
|
// ../server/src/services/companion.ts
|
|
64
|
-
import { EventEmitter as
|
|
52
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
65
53
|
|
|
66
54
|
// ../server/src/services/conversation-store.ts
|
|
67
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
68
|
-
import { readFile as readFile4, readdir, mkdir as mkdir3, rename as rename3, rm as rm2, stat as stat2 } from "fs/promises";
|
|
69
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
70
|
-
import { EventEmitter as EventEmitter2 } from "events";
|
|
71
|
-
|
|
72
|
-
// ../server/src/services/project-store.ts
|
|
73
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
74
|
-
import { EventEmitter } from "events";
|
|
75
|
-
import { mkdir as mkdir2, readFile as readFile3, rename as rename2, rm } from "fs/promises";
|
|
76
|
-
import { dirname, join as join3, resolve as resolve2 } from "path";
|
|
77
|
-
|
|
78
|
-
// ../server/src/utils/ensure-data-gitignore.ts
|
|
79
|
-
import { writeFile } from "fs/promises";
|
|
80
|
-
import { join } from "path";
|
|
81
|
-
var GITIGNORE_CONTENT = `
|
|
82
|
-
#################
|
|
83
|
-
# Do not edit this file #
|
|
84
|
-
#################
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# Encryption key (contains secrets \u2014 never commit)
|
|
88
|
-
.encryption.key
|
|
89
|
-
.encryption.key.enc
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Key verification canary \u2014 MUST be committed so new team members can validate their encryption key
|
|
93
|
-
!.runeya-verify
|
|
94
|
-
|
|
95
|
-
# This machine's cloud session token (encrypted, and specific to this install)
|
|
96
|
-
cloud-session.json
|
|
97
|
-
|
|
98
|
-
# Backup in local only
|
|
99
|
-
backup-*
|
|
100
|
-
|
|
101
|
-
# Per-user environment overrides (never commit \u2014 each dev has their own)
|
|
102
|
-
environment-overrides.json
|
|
103
|
-
|
|
104
|
-
# Private user data (conversations, personal state \u2014 never commit)
|
|
105
|
-
*.private
|
|
106
|
-
*.private.json*
|
|
107
|
-
conversations/
|
|
108
|
-
conversations.private.json
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# Runtime logs
|
|
113
|
-
*.log
|
|
114
|
-
*.log.*
|
|
115
|
-
`;
|
|
116
|
-
async function ensureDataGitignore(dataDir) {
|
|
117
|
-
const gitignorePath = join(dataDir, ".gitignore");
|
|
118
|
-
await writeFile(gitignorePath, GITIGNORE_CONTENT, "utf-8");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ../server/src/services/conversation-section-store.ts
|
|
122
55
|
import { randomUUID } from "crypto";
|
|
123
|
-
import { readFile } from "fs/promises";
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return Object.values(value).every((list) => Array.isArray(list) && list.every((s) => !!s && typeof s === "object" && typeof s.id === "string" && typeof s.name === "string" && typeof s.order === "number"));
|
|
128
|
-
}
|
|
129
|
-
var ConversationSectionStore = class {
|
|
130
|
-
async read() {
|
|
131
|
-
try {
|
|
132
|
-
const parsed = JSON.parse(await readFile(machineFilePath(FILENAME), "utf-8"));
|
|
133
|
-
return isSectionMap(parsed) ? parsed : {};
|
|
134
|
-
} catch {
|
|
135
|
-
return {};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async write(map) {
|
|
139
|
-
await atomicWriteSecure(machineFilePath(FILENAME), JSON.stringify(map, null, 2));
|
|
140
|
-
}
|
|
141
|
-
/** Les sections d'un projet, dans l'ordre choisi. */
|
|
142
|
-
async list(projectId) {
|
|
143
|
-
const map = await this.read();
|
|
144
|
-
return (map[projectId] ?? []).slice().sort((a, b) => a.order - b.order);
|
|
145
|
-
}
|
|
146
|
-
async get(projectId, id) {
|
|
147
|
-
return (await this.list(projectId)).find((s) => s.id === id) ?? null;
|
|
148
|
-
}
|
|
149
|
-
/** Crée une section, posée après les autres. */
|
|
150
|
-
async create(projectId, name) {
|
|
151
|
-
const map = await this.read();
|
|
152
|
-
const list = map[projectId] ?? [];
|
|
153
|
-
const order = list.length > 0 ? Math.max(...list.map((s) => s.order)) + 1 : 0;
|
|
154
|
-
const section = { id: randomUUID(), name, order };
|
|
155
|
-
map[projectId] = [...list, section];
|
|
156
|
-
await this.write(map);
|
|
157
|
-
return section;
|
|
158
|
-
}
|
|
159
|
-
async rename(projectId, id, name) {
|
|
160
|
-
const map = await this.read();
|
|
161
|
-
const section = map[projectId]?.find((s) => s.id === id);
|
|
162
|
-
if (!section) return null;
|
|
163
|
-
section.name = name;
|
|
164
|
-
await this.write(map);
|
|
165
|
-
return section;
|
|
166
|
-
}
|
|
167
|
-
/** Retire une section. Rend `false` si elle n'existait pas. */
|
|
168
|
-
async remove(projectId, id) {
|
|
169
|
-
const map = await this.read();
|
|
170
|
-
const list = map[projectId] ?? [];
|
|
171
|
-
const next = list.filter((s) => s.id !== id);
|
|
172
|
-
if (next.length === list.length) return false;
|
|
173
|
-
if (next.length > 0) map[projectId] = next;
|
|
174
|
-
else delete map[projectId];
|
|
175
|
-
await this.write(map);
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Range les sections du projet dans l'ordre de `ids`.
|
|
180
|
-
*
|
|
181
|
-
* Une section absente de `ids` passe après les autres, dans son ordre
|
|
182
|
-
* d'avant : un appel parti avant la création d'une section ne la fait pas
|
|
183
|
-
* disparaître. Un id inconnu est ignoré.
|
|
184
|
-
*/
|
|
185
|
-
async reorder(projectId, ids) {
|
|
186
|
-
const map = await this.read();
|
|
187
|
-
const list = (map[projectId] ?? []).slice().sort((a, b) => a.order - b.order);
|
|
188
|
-
if (list.length === 0) return;
|
|
189
|
-
const rank = new Map(Array.from(new Set(ids)).map((id, i) => [id, i]));
|
|
190
|
-
const listed = list.filter((s) => rank.has(s.id)).sort((a, b) => rank.get(a.id) - rank.get(b.id));
|
|
191
|
-
const others = list.filter((s) => !rank.has(s.id));
|
|
192
|
-
map[projectId] = [...listed, ...others].map((s, i) => ({ ...s, order: i }));
|
|
193
|
-
await this.write(map);
|
|
194
|
-
}
|
|
195
|
-
/** Oublie les sections d'un projet supprimé. */
|
|
196
|
-
async forgetProject(projectId) {
|
|
197
|
-
const map = await this.read();
|
|
198
|
-
if (!(projectId in map)) return;
|
|
199
|
-
delete map[projectId];
|
|
200
|
-
await this.write(map);
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
var conversationSectionStore = new ConversationSectionStore();
|
|
204
|
-
|
|
205
|
-
// ../server/src/services/project-path-store.ts
|
|
206
|
-
import { readFile as readFile2, writeFile as writeFile2, rename, mkdir, chmod, stat } from "fs/promises";
|
|
207
|
-
import { isAbsolute, join as join2, resolve } from "path";
|
|
208
|
-
var FILENAME2 = "project-paths.json";
|
|
209
|
-
var ProjectPathStore = class {
|
|
210
|
-
paths = null;
|
|
211
|
-
async load() {
|
|
212
|
-
if (this.paths) return this.paths;
|
|
213
|
-
try {
|
|
214
|
-
const raw = await readFile2(machineFilePath(FILENAME2), "utf-8");
|
|
215
|
-
const parsed = JSON.parse(raw);
|
|
216
|
-
this.paths = isPathMap(parsed) ? parsed : {};
|
|
217
|
-
} catch {
|
|
218
|
-
this.paths = {};
|
|
219
|
-
}
|
|
220
|
-
return this.paths;
|
|
221
|
-
}
|
|
222
|
-
async persist() {
|
|
223
|
-
await mkdir(machineRoot(), { recursive: true });
|
|
224
|
-
const filePath = machineFilePath(FILENAME2);
|
|
225
|
-
const tmpPath = `${filePath}.tmp`;
|
|
226
|
-
await writeFile2(tmpPath, JSON.stringify(this.paths ?? {}, null, 2), "utf-8");
|
|
227
|
-
await rename(tmpPath, filePath);
|
|
228
|
-
await chmod(filePath, 384);
|
|
229
|
-
}
|
|
230
|
-
/** The directory chosen for this project, or null while it has none. */
|
|
231
|
-
async get(ref) {
|
|
232
|
-
const paths = await this.load();
|
|
233
|
-
return paths[projectKey(ref)] ?? paths[ref.id] ?? null;
|
|
234
|
-
}
|
|
235
|
-
async all() {
|
|
236
|
-
return { ...await this.load() };
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* The project already placed in this directory, if any other one is.
|
|
240
|
-
*
|
|
241
|
-
* Answers with the key as stored — the couple, or the bare id for an entry an
|
|
242
|
-
* older version wrote — which is enough to say "taken" and to name it.
|
|
243
|
-
*
|
|
244
|
-
* `isKnown` écarte les entrées d'un projet que ce poste ne connaît plus : un
|
|
245
|
-
* projet disparu ne doit pas continuer de retenir son dossier.
|
|
246
|
-
*/
|
|
247
|
-
async projectAt(path, exclude, isKnown = () => true) {
|
|
248
|
-
const target = resolve(path);
|
|
249
|
-
const excluded = exclude ? [projectKey(exclude), exclude.id] : [];
|
|
250
|
-
for (const [key, dir] of Object.entries(await this.load())) {
|
|
251
|
-
if (!excluded.includes(key) && resolve(dir) === target && isKnown(key)) return key;
|
|
252
|
-
}
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
async set(ref, path) {
|
|
256
|
-
if (!isAbsolute(path)) throw new Error("Le chemin du projet doit \xEAtre absolu");
|
|
257
|
-
const paths = await this.load();
|
|
258
|
-
delete paths[ref.id];
|
|
259
|
-
paths[projectKey(ref)] = resolve(path);
|
|
260
|
-
await this.persist();
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Faire suivre le répertoire quand un projet change d'organisation.
|
|
264
|
-
*
|
|
265
|
-
* La clé est le couple `(organisation, projet)` : publier un projet local le
|
|
266
|
-
* fait passer de `local:<id>` à `<org>:<id>`, et l'entrée d'avant cesse alors
|
|
267
|
-
* de répondre — `get` ne retombe que sur l'identifiant nu, forme héritée, pas
|
|
268
|
-
* sur `local:<id>`. Le projet se retrouvait donc sans emplacement connu juste
|
|
269
|
-
* après avoir été publié, alors que ses sources n'avaient pas bougé d'un
|
|
270
|
-
* octet.
|
|
271
|
-
*/
|
|
272
|
-
async relocate(from, to) {
|
|
273
|
-
const paths = await this.load();
|
|
274
|
-
const previous = paths[projectKey(from)] ?? paths[from.id];
|
|
275
|
-
if (!previous) return;
|
|
276
|
-
delete paths[projectKey(from)];
|
|
277
|
-
delete paths[from.id];
|
|
278
|
-
paths[projectKey(to)] = previous;
|
|
279
|
-
await this.persist();
|
|
280
|
-
}
|
|
281
|
-
async clear(ref) {
|
|
282
|
-
const paths = await this.load();
|
|
283
|
-
const keys = [projectKey(ref), ref.id].filter((k) => k in paths);
|
|
284
|
-
if (keys.length === 0) return;
|
|
285
|
-
for (const key of keys) delete paths[key];
|
|
286
|
-
await this.persist();
|
|
287
|
-
}
|
|
288
|
-
/** Test seam: forget what was read from disk. */
|
|
289
|
-
reset() {
|
|
290
|
-
this.paths = null;
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
function isPathMap(value) {
|
|
294
|
-
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
|
|
295
|
-
}
|
|
296
|
-
async function holdsRuneyaProject(path) {
|
|
297
|
-
return stat(join2(path, ".runeya", "project.json")).then(() => true, () => false);
|
|
298
|
-
}
|
|
299
|
-
var projectPathStore = new ProjectPathStore();
|
|
300
|
-
|
|
301
|
-
// ../server/src/services/project-store.ts
|
|
302
|
-
var FILENAME3 = "project.json";
|
|
303
|
-
var LEGACY_FILENAME = "projects.json";
|
|
304
|
-
function toDiskForm(project) {
|
|
305
|
-
const { activeEnvironmentId: _runtimeOnly, ...rest } = project;
|
|
306
|
-
return rest;
|
|
307
|
-
}
|
|
308
|
-
async function readLocalProject() {
|
|
309
|
-
const root = localVaultRoot();
|
|
310
|
-
let current = null;
|
|
311
|
-
try {
|
|
312
|
-
const raw = await readFile3(join3(root, FILENAME3), "utf-8");
|
|
313
|
-
const parsed = ProjectSchema.safeParse(JSON.parse(raw));
|
|
314
|
-
if (parsed.success) current = parsed.data;
|
|
315
|
-
} catch {
|
|
316
|
-
}
|
|
317
|
-
let legacy;
|
|
318
|
-
try {
|
|
319
|
-
legacy = JSON.parse(await readFile3(join3(root, LEGACY_FILENAME), "utf-8"));
|
|
320
|
-
} catch {
|
|
321
|
-
return { local: current, strayCloud: [] };
|
|
322
|
-
}
|
|
323
|
-
const strayCloud = legacy.filter((p) => p.orgId);
|
|
324
|
-
if (current) return { local: current, strayCloud };
|
|
325
|
-
const locals = legacy.filter((p) => !p.orgId);
|
|
326
|
-
if (locals.length > 1) {
|
|
327
|
-
const names = locals.map((p) => `"${p.name}" (${p.id})`).join(", ");
|
|
328
|
-
throw new Error(
|
|
329
|
-
`${join3(root, LEGACY_FILENAME)} holds ${locals.length} local projects: ${names}. A directory now holds a single local project \u2014 move the others to their own directory, or into an organization, then start again.`
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
return { local: locals[0] ?? null, strayCloud };
|
|
333
|
-
}
|
|
334
|
-
function isRegistrable(root) {
|
|
335
|
-
return localProjectVaultRoot(dirname(root)) === resolve2(root);
|
|
336
|
-
}
|
|
337
|
-
async function readLocalProjectAt(root) {
|
|
338
|
-
try {
|
|
339
|
-
const parsed = ProjectSchema.safeParse(JSON.parse(await readFile3(join3(root, FILENAME3), "utf-8")));
|
|
340
|
-
return parsed.success && !parsed.data.orgId ? parsed.data : null;
|
|
341
|
-
} catch {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
async function readCloudProjects() {
|
|
346
|
-
const found = [];
|
|
347
|
-
for (const { orgId, projectId, root } of await listCloudVaults()) {
|
|
348
|
-
try {
|
|
349
|
-
const raw = await readFile3(join3(root, FILENAME3), "utf-8");
|
|
350
|
-
const parsed = ProjectSchema.safeParse({ ...JSON.parse(raw), id: projectId, orgId });
|
|
351
|
-
if (parsed.success) found.push({ project: parsed.data, root });
|
|
352
|
-
} catch {
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return found;
|
|
356
|
-
}
|
|
357
|
-
var LocalProjectRefusedError = class extends Error {
|
|
358
|
-
};
|
|
359
|
-
var LocalProjectExistsError = class extends LocalProjectRefusedError {
|
|
360
|
-
constructor(projectName) {
|
|
361
|
-
super(
|
|
362
|
-
`This directory already holds the local project "${projectName}". A directory holds one local project: use another directory, or create the project in an organization.`
|
|
363
|
-
);
|
|
364
|
-
this.projectName = projectName;
|
|
365
|
-
this.name = "LocalProjectExistsError";
|
|
366
|
-
}
|
|
367
|
-
code = "LOCAL_PROJECT_EXISTS";
|
|
368
|
-
};
|
|
369
|
-
var LocalProjectInMachineRootError = class extends LocalProjectRefusedError {
|
|
370
|
-
code = "LOCAL_PROJECT_IN_MACHINE_ROOT";
|
|
371
|
-
constructor() {
|
|
372
|
-
super(
|
|
373
|
-
"This directory holds Runeya's own data. A local project here would mix its files with the machine's vaults: launch Runeya from another directory, or create the project in an organization."
|
|
374
|
-
);
|
|
375
|
-
this.name = "LocalProjectInMachineRootError";
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
var LocalProjectCloudLinkedError = class extends LocalProjectRefusedError {
|
|
379
|
-
code = "LOCAL_PROJECT_CLOUD_LINKED";
|
|
380
|
-
constructor() {
|
|
381
|
-
super("This directory is already linked to an organization project. Choose another directory.");
|
|
382
|
-
this.name = "LocalProjectCloudLinkedError";
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
var LocalProjectNotFoundError = class extends LocalProjectRefusedError {
|
|
386
|
-
code = "LOCAL_PROJECT_NOT_FOUND";
|
|
387
|
-
constructor() {
|
|
388
|
-
super("This directory holds no local Runeya project to import.");
|
|
389
|
-
this.name = "LocalProjectNotFoundError";
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
var ProjectStore = class {
|
|
393
|
-
/**
|
|
394
|
-
* Keyed by the (organization, project) couple, never by the project id alone:
|
|
395
|
-
* the same id can name a different project in each organization, and keying
|
|
396
|
-
* by id let the second one read from disk overwrite the first — then `save()`
|
|
397
|
-
* wrote that survivor back into both vaults.
|
|
398
|
-
*/
|
|
399
|
-
projects = /* @__PURE__ */ new Map();
|
|
400
|
-
/**
|
|
401
|
-
* Les coffres où un projet local a été lu ou écrit. À l'écriture, un coffre
|
|
402
|
-
* de cette liste qui n'a plus de projet perd sa fiche : c'est ainsi qu'une
|
|
403
|
-
* suppression ou une publication vide le dossier qu'elle quitte.
|
|
404
|
-
*/
|
|
405
|
-
localRoots = /* @__PURE__ */ new Set();
|
|
406
|
-
loaded = false;
|
|
407
|
-
/**
|
|
408
|
-
* L'état du disque tel qu'il était au dernier chargement.
|
|
409
|
-
*
|
|
410
|
-
* Plusieurs Runeya partagent la racine machine — l'application installée et
|
|
411
|
-
* celle qu'on développe, couramment — et créent donc des projets dans les
|
|
412
|
-
* mêmes coffres. Un store chargé une fois pour toutes ne voit jamais le
|
|
413
|
-
* travail de l'autre : il faudrait le redémarrer pour qu'un projet créé à
|
|
414
|
-
* côté existe pour lui.
|
|
415
|
-
*/
|
|
416
|
-
fingerprint = "";
|
|
417
|
-
disk = new StoreDiskCoordinator();
|
|
418
|
-
/** Emits `'saved'` after every persisted mutation, for the cloud sync bridge. */
|
|
419
|
-
events = new EventEmitter();
|
|
420
|
-
getFilePath(root = localVaultRoot()) {
|
|
421
|
-
return join3(root, FILENAME3);
|
|
422
|
-
}
|
|
423
|
-
/** Adopt a project read from disk, remembering which vault it came from. */
|
|
424
|
-
async absorb(into, p, root) {
|
|
425
|
-
const projectEnvs = await environmentStore.getByProjectId(p.id).catch(() => []);
|
|
426
|
-
const defaultEnv = projectEnvs.find((e) => e.default);
|
|
427
|
-
into.set(projectKey(p), {
|
|
428
|
-
...p,
|
|
429
|
-
environmentIds: p.environmentIds ?? [],
|
|
430
|
-
groupOrder: p.groupOrder ?? [],
|
|
431
|
-
// Prefer the env marked default, fall back to first env
|
|
432
|
-
activeEnvironmentId: defaultEnv?.id ?? p.environmentIds?.[0]
|
|
433
|
-
});
|
|
434
|
-
if (!p.orgId) {
|
|
435
|
-
setLocalProjectRoot(p.id, root);
|
|
436
|
-
this.localRoots.add(root);
|
|
437
|
-
}
|
|
438
|
-
setProjectVault(p, root);
|
|
439
|
-
setProjectServices(p, p.serviceIds ?? []);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Ce que le disque porte : quels coffres, et quand leur fiche a changé.
|
|
443
|
-
*
|
|
444
|
-
* Les coffres sont demandés à `listCloudVaults`, qui les découvre déjà par
|
|
445
|
-
* balayage — en refaire un ici donnerait deux inventaires à tenir d'accord.
|
|
446
|
-
* Seules les dates sont relevées : lire et analyser les fiches, puis les
|
|
447
|
-
* absorber (ce qui va interroger les environnements), est justement ce que ce
|
|
448
|
-
* repère évite de refaire à chaque appel.
|
|
449
|
-
*/
|
|
450
|
-
async diskFingerprint() {
|
|
451
|
-
return fingerprintFiles([
|
|
452
|
-
this.getFilePath(),
|
|
453
|
-
...(await readLocalProjectDirs()).map((dir) => this.getFilePath(localProjectVaultRoot(dir))),
|
|
454
|
-
...(await listCloudVaults()).map((v) => this.getFilePath(v.root))
|
|
455
|
-
]);
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* Read every vault: the launch directory, then each organization's projects.
|
|
459
|
-
*
|
|
460
|
-
* Cloud vaults are discovered by walking `~/.runeya/orgs/*/projects/*` rather
|
|
461
|
-
* than from an index. An index is one more thing that can disagree with the
|
|
462
|
-
* disk — and a project present on disk but missing from it is exactly the
|
|
463
|
-
* "already here, still shown as downloadable" bug.
|
|
464
|
-
*
|
|
465
|
-
* Relu quand le disque a bougé, pas une fois pour toutes : une autre instance
|
|
466
|
-
* partageant cette racine machine y crée des projets, et ne jamais relire
|
|
467
|
-
* reviendrait à exiger un redémarrage pour les voir.
|
|
468
|
-
*/
|
|
469
|
-
async load() {
|
|
470
|
-
let migrated = false;
|
|
471
|
-
await this.disk.load(async () => {
|
|
472
|
-
const fingerprint = await this.diskFingerprint();
|
|
473
|
-
if (this.loaded && fingerprint === this.fingerprint) return null;
|
|
474
|
-
const next = await this.readVaults();
|
|
475
|
-
return () => {
|
|
476
|
-
for (const [key, project] of next.projects) {
|
|
477
|
-
const before = this.projects.get(key)?.activeEnvironmentId;
|
|
478
|
-
if (before && project.environmentIds.includes(before)) {
|
|
479
|
-
project.activeEnvironmentId = before;
|
|
480
|
-
} else {
|
|
481
|
-
this.unsettled.add(key);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
this.projects = next.projects;
|
|
485
|
-
this.fingerprint = fingerprint;
|
|
486
|
-
this.loaded = true;
|
|
487
|
-
migrated = next.migrated;
|
|
488
|
-
};
|
|
489
|
-
});
|
|
490
|
-
if (migrated) {
|
|
491
|
-
await this.save();
|
|
492
|
-
await serviceStore.relocateAll();
|
|
493
|
-
await environmentStore.relocateAll();
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
/** Le parcours proprement dit, dans une table neuve que `load` substitue. */
|
|
497
|
-
async readVaults() {
|
|
498
|
-
const projects = /* @__PURE__ */ new Map();
|
|
499
|
-
this.localRoots = /* @__PURE__ */ new Set();
|
|
500
|
-
const { local, strayCloud } = await readLocalProject();
|
|
501
|
-
if (local) {
|
|
502
|
-
await this.absorb(projects, local, localVaultRoot());
|
|
503
|
-
if (!localVaultCollidesWithMachineRoot() && isRegistrable(localVaultRoot())) {
|
|
504
|
-
await registerLocalProjectDir(dirname(localVaultRoot())).catch(() => {
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
for (const dir of await readLocalProjectDirs()) {
|
|
509
|
-
const root = localProjectVaultRoot(dir);
|
|
510
|
-
if (root === localVaultRoot()) continue;
|
|
511
|
-
const found = await readLocalProjectAt(root);
|
|
512
|
-
if (!found || projects.has(projectKey(found))) continue;
|
|
513
|
-
await this.absorb(projects, found, root);
|
|
514
|
-
}
|
|
515
|
-
let migrated = false;
|
|
516
|
-
for (const project of strayCloud) {
|
|
517
|
-
try {
|
|
518
|
-
await this.absorb(projects, project, vaultFor(project).root);
|
|
519
|
-
migrated = true;
|
|
520
|
-
} catch {
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
for (const { project, root } of await readCloudProjects()) {
|
|
524
|
-
await this.absorb(projects, project, root);
|
|
525
|
-
}
|
|
526
|
-
return { projects, migrated };
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Persist every vault this store knows about.
|
|
530
|
-
*
|
|
531
|
-
* Writing all of them rather than only the one that changed keeps a project
|
|
532
|
-
* that moved between vaults — a local project adopted by an org, a cloud
|
|
533
|
-
* project pulled for the first time — from lingering in the vault it left.
|
|
534
|
-
*/
|
|
535
|
-
save() {
|
|
536
|
-
return this.disk.save(() => this.writeVaults());
|
|
537
|
-
}
|
|
538
|
-
async writeVaults() {
|
|
539
|
-
const locals = [];
|
|
540
|
-
const cloud = [];
|
|
541
|
-
for (const project of this.projects.values()) {
|
|
542
|
-
if (!project.orgId) locals.push(project);
|
|
543
|
-
else cloud.push({ project, root: vaultFor(project).root });
|
|
544
|
-
}
|
|
545
|
-
const written = /* @__PURE__ */ new Map();
|
|
546
|
-
for (const local of locals) {
|
|
547
|
-
const root = vaultFor(local).root;
|
|
548
|
-
const other = written.get(root);
|
|
549
|
-
if (other) {
|
|
550
|
-
throw new Error(
|
|
551
|
-
`Refusing to save two local projects in ${root}: "${other.name}" and "${local.name}". A directory holds a single local project.`
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
written.set(root, local);
|
|
555
|
-
}
|
|
556
|
-
for (const [root, local] of written) {
|
|
557
|
-
await mkdir2(root, { recursive: true });
|
|
558
|
-
await ensureDataGitignore(root);
|
|
559
|
-
await atomicWriteSecure(this.getFilePath(root), JSON.stringify(toDiskForm(local), null, 2));
|
|
560
|
-
this.localRoots.add(root);
|
|
561
|
-
}
|
|
562
|
-
for (const root of this.localRoots) {
|
|
563
|
-
if (written.has(root)) continue;
|
|
564
|
-
await rm(this.getFilePath(root), { force: true }).catch(() => {
|
|
565
|
-
});
|
|
566
|
-
if (isRegistrable(root)) await unregisterLocalProjectDir(dirname(root)).catch(() => {
|
|
567
|
-
});
|
|
568
|
-
this.localRoots.delete(root);
|
|
569
|
-
}
|
|
570
|
-
await rename2(join3(localVaultRoot(), LEGACY_FILENAME), join3(localVaultRoot(), LEGACY_FILENAME + ".bak")).catch(() => {
|
|
571
|
-
});
|
|
572
|
-
for (const { project, root } of cloud) {
|
|
573
|
-
await atomicWriteSecure(this.getFilePath(root), JSON.stringify(toDiskForm(project), null, 2));
|
|
574
|
-
setProjectVault(project, root);
|
|
575
|
-
}
|
|
576
|
-
this.fingerprint = await this.diskFingerprint();
|
|
577
|
-
this.events.emit("saved");
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* The project a caller means, from a couple or from a bare id.
|
|
581
|
-
*
|
|
582
|
-
* A ref carrying its `orgId` — even an explicit `null` for a local project —
|
|
583
|
-
* names exactly one project and is looked up directly. A bare id is what a
|
|
584
|
-
* contained entity (an environment, a conversation) still carries, and it is
|
|
585
|
-
* resolved only while it answers for a single project: electing one of two
|
|
586
|
-
* would write into the wrong organization's vault.
|
|
587
|
-
*/
|
|
588
|
-
resolveKey(target) {
|
|
589
|
-
if (typeof target !== "string" && target.orgId !== void 0) return projectKey(target);
|
|
590
|
-
const id = typeof target === "string" ? target : target.id;
|
|
591
|
-
const matches = Array.from(this.projects.values()).filter((p) => p.id === id);
|
|
592
|
-
if (matches.length > 1) {
|
|
593
|
-
throw new AmbiguousProjectError(id, matches.map((p) => p.orgId ?? null));
|
|
594
|
-
}
|
|
595
|
-
return matches[0] ? projectKey(matches[0]) : null;
|
|
596
|
-
}
|
|
597
|
-
find(target) {
|
|
598
|
-
const key = this.resolveKey(target);
|
|
599
|
-
const project = key ? this.projects.get(key) : void 0;
|
|
600
|
-
return key && project ? { key, project } : null;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Le projet déjà placé dans ce dossier, parmi ceux que ce poste connaît.
|
|
604
|
-
*
|
|
605
|
-
* Une entrée de `project-paths.json` peut survivre à son projet (supprimé
|
|
606
|
-
* ailleurs, ou avant que la suppression ne la nettoie). Elle ne réserve plus
|
|
607
|
-
* rien : le refus qui en découlait nommait un projet introuvable.
|
|
608
|
-
*/
|
|
609
|
-
async projectAtPath(path, exclude) {
|
|
610
|
-
await this.load();
|
|
611
|
-
const ids = new Set(Array.from(this.projects.values(), (p) => p.id));
|
|
612
|
-
return projectPathStore.projectAt(path, exclude, (key) => this.projects.has(key) || ids.has(key));
|
|
613
|
-
}
|
|
614
|
-
async list() {
|
|
615
|
-
await this.load();
|
|
616
|
-
await this.settleActiveEnvironments();
|
|
617
|
-
return Array.from(this.projects.values());
|
|
618
|
-
}
|
|
619
|
-
async get(target) {
|
|
620
|
-
await this.load();
|
|
621
|
-
await this.settleActiveEnvironments();
|
|
622
|
-
return this.find(target)?.project ?? null;
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Projets dont l'environnement actif vient d'un repli et n'a pas été vérifié.
|
|
626
|
-
*
|
|
627
|
-
* Au chargement, les environnements ne sont pas encore lisibles : leur magasin
|
|
628
|
-
* attend justement la fin de ce chargement. `absorb` retient donc le premier
|
|
629
|
-
* id de `environmentIds`, qui peut désigner un environnement disparu. Le
|
|
630
|
-
* sélecteur affichait alors cet id brut, absent de sa liste.
|
|
631
|
-
*/
|
|
632
|
-
unsettled = /* @__PURE__ */ new Set();
|
|
633
|
-
settling = false;
|
|
634
|
-
/** Retenir, pour chaque projet non vérifié, un environnement qui existe. */
|
|
635
|
-
async settleActiveEnvironments() {
|
|
636
|
-
if (this.settling || this.unsettled.size === 0) return;
|
|
637
|
-
this.settling = true;
|
|
638
|
-
try {
|
|
639
|
-
for (const key of this.unsettled) {
|
|
640
|
-
const project = this.projects.get(key);
|
|
641
|
-
if (!project) {
|
|
642
|
-
this.unsettled.delete(key);
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
const envs = (await environmentStore.getByProjectId(project.id, project.orgId ?? null).catch(() => [])).filter((e) => e.scope !== "service");
|
|
646
|
-
if (envs.length === 0) continue;
|
|
647
|
-
const current = envs.find((e) => e.id === project.activeEnvironmentId);
|
|
648
|
-
const chosen = envs.find((e) => e.default) ?? current ?? envs[0];
|
|
649
|
-
project.activeEnvironmentId = chosen.id;
|
|
650
|
-
this.unsettled.delete(key);
|
|
651
|
-
}
|
|
652
|
-
} finally {
|
|
653
|
-
this.settling = false;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
/**
|
|
657
|
-
* Vérifier qu'un dossier peut accueillir un projet local, et rendre son coffre.
|
|
658
|
-
*
|
|
659
|
-
* Refusé si son `.runeya` serait la racine machine, si le dossier est déjà
|
|
660
|
-
* l'emplacement d'un projet d'organisation, ou s'il porte déjà un projet.
|
|
661
|
-
*/
|
|
662
|
-
async localRootFor(root) {
|
|
663
|
-
if (collidesWithMachineRoot(root)) throw new LocalProjectInMachineRootError();
|
|
664
|
-
if (await this.projectAtPath(dirname(root))) throw new LocalProjectCloudLinkedError();
|
|
665
|
-
const known = Array.from(this.projects.values()).find((p) => !p.orgId && vaultFor(p).root === root);
|
|
666
|
-
if (known) throw new LocalProjectExistsError(known.name);
|
|
667
|
-
const onDisk = await readLocalProjectAt(root);
|
|
668
|
-
if (onDisk) throw new LocalProjectExistsError(onDisk.name);
|
|
669
|
-
if (root !== localVaultRoot() && await holdsRuneyaProject(dirname(root))) throw new LocalProjectExistsError("");
|
|
670
|
-
return root;
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* `localDir` : le dossier d'un projet local. Omis, c'est le dossier de
|
|
674
|
-
* lancement, comme avant que les projets locaux aient chacun le leur.
|
|
675
|
-
*/
|
|
676
|
-
async create(input, localDir) {
|
|
677
|
-
await this.load();
|
|
678
|
-
const localRoot = input.orgId ? null : await this.localRootFor(localDir ? localProjectVaultRoot(localDir) : localVaultRoot());
|
|
679
|
-
const project = {
|
|
680
|
-
id: randomUUID2(),
|
|
681
|
-
orgId: input.orgId ?? null,
|
|
682
|
-
name: input.name,
|
|
683
|
-
description: input.description ?? "",
|
|
684
|
-
serviceIds: input.serviceIds ?? [],
|
|
685
|
-
environmentIds: input.environmentIds ?? [],
|
|
686
|
-
activeEnvironmentId: input.activeEnvironmentId,
|
|
687
|
-
groupOrder: input.groupOrder ?? []
|
|
688
|
-
};
|
|
689
|
-
this.projects.set(projectKey(project), project);
|
|
690
|
-
if (localRoot) {
|
|
691
|
-
setLocalProjectRoot(project.id, localRoot);
|
|
692
|
-
if (isRegistrable(localRoot)) await registerLocalProjectDir(dirname(localRoot));
|
|
693
|
-
}
|
|
694
|
-
setProjectVault(project, vaultFor(project).root);
|
|
695
|
-
setProjectServices(project, project.serviceIds);
|
|
696
|
-
await this.save();
|
|
697
|
-
return project;
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Ce qu'un dossier permet pour un projet local : en créer un (`available`),
|
|
701
|
-
* importer celui qu'il porte (`importable`), rien parce qu'il est déjà dans
|
|
702
|
-
* la liste (`listed`), ou rien parce qu'il est refusé.
|
|
703
|
-
*/
|
|
704
|
-
async inspectLocalDir(dir) {
|
|
705
|
-
await this.load();
|
|
706
|
-
const root = localProjectVaultRoot(dir);
|
|
707
|
-
if (collidesWithMachineRoot(root)) return { status: "machine-root", projectName: null };
|
|
708
|
-
if (await this.projectAtPath(resolve2(dir))) return { status: "cloud-linked", projectName: null };
|
|
709
|
-
const known = Array.from(this.projects.values()).find((p) => !p.orgId && vaultFor(p).root === root);
|
|
710
|
-
if (known) return { status: "listed", projectName: known.name };
|
|
711
|
-
const onDisk = await readLocalProjectAt(root);
|
|
712
|
-
if (onDisk) return { status: "importable", projectName: onDisk.name };
|
|
713
|
-
if (await holdsRuneyaProject(dirname(root))) return { status: "unreadable", projectName: null };
|
|
714
|
-
return { status: "available", projectName: null };
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Ajouter à la liste le projet local déjà présent dans ce dossier.
|
|
718
|
-
*
|
|
719
|
-
* Son `.runeya` n'est pas touché : le dossier est seulement enregistré, et le
|
|
720
|
-
* projet relu comme les autres.
|
|
721
|
-
*/
|
|
722
|
-
async importLocal(dir) {
|
|
723
|
-
await this.load();
|
|
724
|
-
const root = localProjectVaultRoot(dir);
|
|
725
|
-
if (collidesWithMachineRoot(root)) throw new LocalProjectInMachineRootError();
|
|
726
|
-
if (await this.projectAtPath(resolve2(dir))) throw new LocalProjectCloudLinkedError();
|
|
727
|
-
const found = await readLocalProjectAt(root);
|
|
728
|
-
if (!found) throw new LocalProjectNotFoundError();
|
|
729
|
-
const known = this.projects.get(projectKey(found));
|
|
730
|
-
if (known) return known;
|
|
731
|
-
await registerLocalProjectDir(dirname(root));
|
|
732
|
-
this.loaded = false;
|
|
733
|
-
await this.load();
|
|
734
|
-
return this.projects.get(projectKey(found)) ?? found;
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* `input.orgId` names which project is being updated, it does not move it:
|
|
738
|
-
* a project is born local or in an organization and stays there, so treating
|
|
739
|
-
* it as a new value would silently relocate a vault instead of editing it.
|
|
740
|
-
*/
|
|
741
|
-
async update(input) {
|
|
742
|
-
await this.load();
|
|
743
|
-
const found = this.find(input);
|
|
744
|
-
if (!found) return null;
|
|
745
|
-
const { key, project: existing } = found;
|
|
746
|
-
const updated = {
|
|
747
|
-
...existing,
|
|
748
|
-
...input.name !== void 0 && { name: input.name },
|
|
749
|
-
...input.description !== void 0 && { description: input.description },
|
|
750
|
-
...input.serviceIds !== void 0 && { serviceIds: input.serviceIds },
|
|
751
|
-
...input.environmentIds !== void 0 && { environmentIds: input.environmentIds },
|
|
752
|
-
...input.activeEnvironmentId !== void 0 && { activeEnvironmentId: input.activeEnvironmentId },
|
|
753
|
-
...input.groupOrder !== void 0 && { groupOrder: input.groupOrder }
|
|
754
|
-
};
|
|
755
|
-
recordDetached(existing.id, "serviceIds", existing.serviceIds ?? [], updated.serviceIds ?? []);
|
|
756
|
-
recordDetached(existing.id, "environmentIds", existing.environmentIds ?? [], updated.environmentIds ?? []);
|
|
757
|
-
this.projects.set(key, updated);
|
|
758
|
-
setProjectVault(updated, vaultFor(updated).root);
|
|
759
|
-
setProjectServices(updated, updated.serviceIds);
|
|
760
|
-
await this.save();
|
|
761
|
-
return updated;
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Faire passer le projet local de ce dossier dans une organisation.
|
|
765
|
-
*
|
|
766
|
-
* Le seul déplacement de coffre qu'on s'autorise, et il ne va que dans ce
|
|
767
|
-
* sens : le projet quitte `<cwd>/.runeya` pour
|
|
768
|
-
* `~/.runeya/orgs/<org>/projects/<id>/`, en gardant son identifiant. Il n'y a
|
|
769
|
-
* donc jamais deux copies à réconcilier — c'est ce qui rendait « publier »
|
|
770
|
-
* dangereux tant que le projet restait aussi local.
|
|
771
|
-
*
|
|
772
|
-
* Refusé si l'organisation porte déjà ce projet : la copie d'ici écraserait
|
|
773
|
-
* celle du cloud, et ces deux histoires ne sont pas à nous de les fusionner.
|
|
774
|
-
*
|
|
775
|
-
* Ce qui suit le projet est écrit par les stores eux-mêmes : `save()` réécrit
|
|
776
|
-
* chaque coffre — le fichier local disparaît, celui de l'org apparaît — puis
|
|
777
|
-
* les contenus sont relogés depuis le registre, qui pointe désormais ailleurs.
|
|
778
|
-
*/
|
|
779
|
-
async moveToOrg(projectId, orgId) {
|
|
780
|
-
await this.load();
|
|
781
|
-
const local = this.projects.get(projectKey({ id: projectId, orgId: null }));
|
|
782
|
-
if (!local) throw new Error("PROJECT_NOT_LOCAL");
|
|
783
|
-
if (this.projects.get(projectKey({ id: projectId, orgId }))) {
|
|
784
|
-
throw new Error("PROJECT_ALREADY_IN_ORG");
|
|
785
|
-
}
|
|
786
|
-
const localRoot = vaultFor(local).root;
|
|
787
|
-
const localDir = localRoot === localVaultRoot() ? null : dirname(localRoot);
|
|
788
|
-
const moved = { ...local, orgId };
|
|
789
|
-
this.projects.delete(projectKey(local));
|
|
790
|
-
forgetProject(local);
|
|
791
|
-
forgetLocalProjectRoot(local.id);
|
|
792
|
-
this.projects.set(projectKey(moved), moved);
|
|
793
|
-
setProjectVault(moved, vaultFor(moved).root);
|
|
794
|
-
setProjectServices(moved, moved.serviceIds ?? []);
|
|
795
|
-
await this.save();
|
|
796
|
-
if (localDir && !await projectPathStore.get(moved)) {
|
|
797
|
-
await projectPathStore.set(moved, localDir);
|
|
798
|
-
}
|
|
799
|
-
await serviceStore.reassignOrg(moved.serviceIds ?? [], orgId);
|
|
800
|
-
await environmentStore.relocateAll();
|
|
801
|
-
return moved;
|
|
802
|
-
}
|
|
803
|
-
async delete(target) {
|
|
804
|
-
await this.load();
|
|
805
|
-
const found = this.find(target);
|
|
806
|
-
if (!found) return false;
|
|
807
|
-
const { key, project } = found;
|
|
808
|
-
this.projects.delete(key);
|
|
809
|
-
await this.save();
|
|
810
|
-
await this.discardVault(project);
|
|
811
|
-
await projectPathStore.clear(project);
|
|
812
|
-
await conversationSectionStore.forgetProject(project.id);
|
|
813
|
-
return true;
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Remove what a deleted cloud project left behind.
|
|
817
|
-
*
|
|
818
|
-
* Its vault is a directory of its own, so dropping the project means dropping
|
|
819
|
-
* the directory — otherwise `readCloudVaults` would resurrect it on the next
|
|
820
|
-
* load. Local projects share their vault with the directory's own files and
|
|
821
|
-
* are only removed from the list.
|
|
822
|
-
*/
|
|
823
|
-
async discardVault(project) {
|
|
824
|
-
forgetProject(project);
|
|
825
|
-
if (!project.orgId) forgetLocalProjectRoot(project.id);
|
|
826
|
-
if (!project.orgId) return;
|
|
827
|
-
await rm(vaultFor(project).root, { recursive: true, force: true }).catch(() => {
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
/**
|
|
831
|
-
* Stop holding a project on this machine, leaving the doc alone.
|
|
832
|
-
*
|
|
833
|
-
* Memory only: the caller removes the vault itself. Its contents are dropped
|
|
834
|
-
* too, because a service or environment surviving in memory would be written
|
|
835
|
-
* back on the next save — and, its project being unknown by then, into the
|
|
836
|
-
* local vault.
|
|
837
|
-
*/
|
|
838
|
-
async forgetLocally(target) {
|
|
839
|
-
await this.load();
|
|
840
|
-
const found = this.find(target);
|
|
841
|
-
if (!found) return;
|
|
842
|
-
const { key, project } = found;
|
|
843
|
-
this.projects.delete(key);
|
|
844
|
-
forgetProject(project);
|
|
845
|
-
await serviceStore.forgetLocally(project.serviceIds ?? []);
|
|
846
|
-
await environmentStore.forgetByProject(project.id);
|
|
847
|
-
await projectPathStore.clear(project);
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* Le projet qui porte un service, ou `null` s'il n'est rattaché à aucun.
|
|
851
|
-
*
|
|
852
|
-
* Un service n'appartient qu'à un projet : la recherche s'arrête au premier.
|
|
853
|
-
*/
|
|
854
|
-
async ofService(serviceId) {
|
|
855
|
-
await this.load();
|
|
856
|
-
for (const project of this.projects.values()) {
|
|
857
|
-
if (project.serviceIds.includes(serviceId)) return project;
|
|
858
|
-
}
|
|
859
|
-
return null;
|
|
860
|
-
}
|
|
861
|
-
/** All projects linked to a given organization (for the sync bridge). */
|
|
862
|
-
async listByOrg(orgId) {
|
|
863
|
-
await this.load();
|
|
864
|
-
return Array.from(this.projects.values()).filter((p) => p.orgId === orgId);
|
|
865
|
-
}
|
|
866
|
-
/** Projects of an organization in their on-disk form (what the cloud mirrors). */
|
|
867
|
-
async listDiskFormByOrg(orgId) {
|
|
868
|
-
return (await this.listByOrg(orgId)).map(toDiskForm);
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* Apply a project received from the cloud, in its on-disk form.
|
|
872
|
-
*
|
|
873
|
-
* Validated rather than trusted: a peer on another version — or a tampered
|
|
874
|
-
* doc — must not be able to write arbitrary shapes into `projects.json`.
|
|
875
|
-
* `activeEnvironmentId` is runtime-only and stays whatever it is locally.
|
|
876
|
-
* Diff-guarded: an identical project neither saves nor emits (loop-safe).
|
|
877
|
-
* Returns whether a new local project was created, so the caller can
|
|
878
|
-
* bootstrap project-local state (e.g. a default environment).
|
|
879
|
-
*/
|
|
880
|
-
async applyRemoteUpsert(id, fields) {
|
|
881
|
-
await this.load();
|
|
882
|
-
const parsed = ProjectSchema.safeParse({ ...fields, id });
|
|
883
|
-
if (!parsed.success) {
|
|
884
|
-
console.error("[project-store] Rejected remote project %s:", id, parsed.error.message);
|
|
885
|
-
return null;
|
|
886
|
-
}
|
|
887
|
-
const incoming = toDiskForm(parsed.data);
|
|
888
|
-
const key = projectKey(parsed.data);
|
|
889
|
-
const existing = this.projects.get(key);
|
|
890
|
-
if (existing) {
|
|
891
|
-
if (JSON.stringify(toDiskForm(existing)) === JSON.stringify(incoming)) {
|
|
892
|
-
return { project: existing, created: false };
|
|
893
|
-
}
|
|
894
|
-
const updated = { ...incoming, activeEnvironmentId: existing.activeEnvironmentId };
|
|
895
|
-
this.projects.set(key, updated);
|
|
896
|
-
setProjectVault(updated, vaultFor(updated).root);
|
|
897
|
-
setProjectServices(updated, updated.serviceIds);
|
|
898
|
-
await this.save();
|
|
899
|
-
return { project: updated, created: false };
|
|
900
|
-
}
|
|
901
|
-
const project = { ...incoming, activeEnvironmentId: void 0 };
|
|
902
|
-
this.projects.set(key, project);
|
|
903
|
-
setProjectVault(project, vaultFor(project).root);
|
|
904
|
-
setProjectServices(project, project.serviceIds);
|
|
905
|
-
await this.save();
|
|
906
|
-
return { project, created: true };
|
|
907
|
-
}
|
|
908
|
-
/** Delete a project that was removed on another peer. */
|
|
909
|
-
async applyRemoteDelete(target) {
|
|
910
|
-
await this.load();
|
|
911
|
-
const found = this.find(target);
|
|
912
|
-
if (!found) return false;
|
|
913
|
-
const { key, project } = found;
|
|
914
|
-
this.projects.delete(key);
|
|
915
|
-
await this.save();
|
|
916
|
-
await this.discardVault(project);
|
|
917
|
-
await projectPathStore.clear(project);
|
|
918
|
-
return true;
|
|
919
|
-
}
|
|
920
|
-
async addService(target, serviceId) {
|
|
921
|
-
await this.load();
|
|
922
|
-
const found = this.find(target);
|
|
923
|
-
if (!found) return null;
|
|
924
|
-
const { key, project } = found;
|
|
925
|
-
if (project.serviceIds.includes(serviceId)) return project;
|
|
926
|
-
const updated = { ...project, serviceIds: [...project.serviceIds, serviceId] };
|
|
927
|
-
this.projects.set(key, updated);
|
|
928
|
-
setProjectServices(updated, updated.serviceIds);
|
|
929
|
-
await this.save();
|
|
930
|
-
return updated;
|
|
931
|
-
}
|
|
932
|
-
async removeService(target, serviceId) {
|
|
933
|
-
await this.load();
|
|
934
|
-
const found = this.find(target);
|
|
935
|
-
if (!found) return null;
|
|
936
|
-
const { key, project } = found;
|
|
937
|
-
const updated = { ...project, serviceIds: project.serviceIds.filter((id) => id !== serviceId) };
|
|
938
|
-
recordDetached(project.id, "serviceIds", project.serviceIds, updated.serviceIds);
|
|
939
|
-
this.projects.set(key, updated);
|
|
940
|
-
setProjectServices(updated, updated.serviceIds);
|
|
941
|
-
await this.save();
|
|
942
|
-
return updated;
|
|
943
|
-
}
|
|
944
|
-
async addEnvironment(target, environmentId) {
|
|
945
|
-
await this.load();
|
|
946
|
-
const found = this.find(target);
|
|
947
|
-
if (!found) return null;
|
|
948
|
-
const { key, project } = found;
|
|
949
|
-
if (project.environmentIds?.includes(environmentId)) return project;
|
|
950
|
-
const updated = {
|
|
951
|
-
...project,
|
|
952
|
-
environmentIds: [...project.environmentIds ?? [], environmentId]
|
|
953
|
-
};
|
|
954
|
-
this.projects.set(key, updated);
|
|
955
|
-
await this.save();
|
|
956
|
-
return updated;
|
|
957
|
-
}
|
|
958
|
-
async removeEnvironment(target, environmentId) {
|
|
959
|
-
await this.load();
|
|
960
|
-
const found = this.find(target);
|
|
961
|
-
if (!found) return null;
|
|
962
|
-
const { key, project } = found;
|
|
963
|
-
const updated = {
|
|
964
|
-
...project,
|
|
965
|
-
environmentIds: (project.environmentIds ?? []).filter((id) => id !== environmentId),
|
|
966
|
-
// Clear activeEnvironmentId if it was the removed environment
|
|
967
|
-
...project.activeEnvironmentId === environmentId && { activeEnvironmentId: void 0 }
|
|
968
|
-
};
|
|
969
|
-
recordDetached(project.id, "environmentIds", project.environmentIds ?? [], updated.environmentIds);
|
|
970
|
-
this.projects.set(key, updated);
|
|
971
|
-
await this.save();
|
|
972
|
-
return updated;
|
|
973
|
-
}
|
|
974
|
-
async setActiveEnvironment(target, environmentId) {
|
|
975
|
-
await this.load();
|
|
976
|
-
const found = this.find(target);
|
|
977
|
-
if (!found) return null;
|
|
978
|
-
const { key, project } = found;
|
|
979
|
-
const updated = {
|
|
980
|
-
...project,
|
|
981
|
-
activeEnvironmentId: environmentId
|
|
982
|
-
};
|
|
983
|
-
this.projects.set(key, updated);
|
|
984
|
-
this.unsettled.delete(key);
|
|
985
|
-
return updated;
|
|
986
|
-
}
|
|
987
|
-
};
|
|
988
|
-
var projectStore = new ProjectStore();
|
|
989
|
-
registerProjectLoader(() => projectStore.list().then(() => void 0));
|
|
990
|
-
|
|
991
|
-
// ../server/src/services/conversation-store.ts
|
|
992
|
-
var FILENAME4 = "conversations.private.json";
|
|
56
|
+
import { readFile, readdir, mkdir, rename, rm, stat } from "fs/promises";
|
|
57
|
+
import { dirname, join } from "path";
|
|
58
|
+
import { EventEmitter } from "events";
|
|
59
|
+
var FILENAME = "conversations.private.json";
|
|
993
60
|
var CONVERSATIONS_DIRNAME = "conversations";
|
|
994
61
|
var FILE_SUFFIX = ".private.json";
|
|
995
62
|
var SAFE_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
996
63
|
function conversationDir(root) {
|
|
997
|
-
return
|
|
64
|
+
return join(root, CONVERSATIONS_DIRNAME);
|
|
998
65
|
}
|
|
999
66
|
function conversationPath(root, id) {
|
|
1000
|
-
return
|
|
67
|
+
return join(root, CONVERSATIONS_DIRNAME, `${id}${FILE_SUFFIX}`);
|
|
1001
68
|
}
|
|
1002
69
|
function migratedAggregateName(now) {
|
|
1003
70
|
return `conversations.migrated-${now.toISOString().replace(/[:.]/g, "-").substring(0, 19)}.private.json`;
|
|
1004
71
|
}
|
|
1005
72
|
async function fileStamp(path) {
|
|
1006
73
|
try {
|
|
1007
|
-
const s = await
|
|
74
|
+
const s = await stat(path);
|
|
1008
75
|
return `${s.mtimeMs}:${s.size}:${s.ino}`;
|
|
1009
76
|
} catch {
|
|
1010
77
|
return null;
|
|
@@ -1039,7 +106,7 @@ var ConversationStore = class {
|
|
|
1039
106
|
* conversation écrite dans l'autre fenêtre.
|
|
1040
107
|
*/
|
|
1041
108
|
fingerprint = "";
|
|
1042
|
-
events = new
|
|
109
|
+
events = new EventEmitter().setMaxListeners(0);
|
|
1043
110
|
disk = new StoreDiskCoordinator();
|
|
1044
111
|
/** Le fichier où chaque conversation est écrite aujourd'hui, s'il existe. */
|
|
1045
112
|
written = /* @__PURE__ */ new Map();
|
|
@@ -1070,11 +137,11 @@ var ConversationStore = class {
|
|
|
1070
137
|
* root, but a launch directory owning its own key keeps both together.
|
|
1071
138
|
*/
|
|
1072
139
|
getFilePath() {
|
|
1073
|
-
return keyedFilePath(
|
|
140
|
+
return keyedFilePath(FILENAME);
|
|
1074
141
|
}
|
|
1075
142
|
/** Where older versions kept it — read only, so an older Runeya keeps working. */
|
|
1076
143
|
getLegacyFilePath() {
|
|
1077
|
-
return legacyMachineFilePath(
|
|
144
|
+
return legacyMachineFilePath(FILENAME);
|
|
1078
145
|
}
|
|
1079
146
|
/**
|
|
1080
147
|
* Read the machine file, falling back to the launch directory of old.
|
|
@@ -1084,13 +151,13 @@ var ConversationStore = class {
|
|
|
1084
151
|
*/
|
|
1085
152
|
async readFromDisk() {
|
|
1086
153
|
try {
|
|
1087
|
-
return await
|
|
154
|
+
return await readFile(await this.getFilePath(), "utf-8");
|
|
1088
155
|
} catch {
|
|
1089
|
-
return
|
|
156
|
+
return readFile(this.getLegacyFilePath(), "utf-8");
|
|
1090
157
|
}
|
|
1091
158
|
}
|
|
1092
159
|
async ensureDir() {
|
|
1093
|
-
await
|
|
160
|
+
await mkdir(dirname(await this.getFilePath()), { recursive: true });
|
|
1094
161
|
}
|
|
1095
162
|
encryptMessages(messages, vault) {
|
|
1096
163
|
return encryptValue(JSON.stringify(messages), vault);
|
|
@@ -1197,10 +264,10 @@ ${new Error("trace").stack}`
|
|
|
1197
264
|
/** Les fichiers dont ce store tire son contenu : les coffres, et l'hérité. */
|
|
1198
265
|
async sourcePaths() {
|
|
1199
266
|
const vaults = await allVaultRoots();
|
|
1200
|
-
const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(
|
|
267
|
+
const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(FILENAME)];
|
|
1201
268
|
const all = [
|
|
1202
269
|
...legacyPaths.map((path) => ({ path, legacy: true })),
|
|
1203
|
-
...vaults.map((root) => ({ path:
|
|
270
|
+
...vaults.map((root) => ({ path: join(root, FILENAME), legacy: false }))
|
|
1204
271
|
];
|
|
1205
272
|
return all.filter((source, i) => !all.slice(i + 1).some((later) => later.path === source.path));
|
|
1206
273
|
}
|
|
@@ -1209,7 +276,7 @@ ${new Error("trace").stack}`
|
|
|
1209
276
|
* doublon. Le dossier de lancement hérité en fait partie.
|
|
1210
277
|
*/
|
|
1211
278
|
async sourceRoots() {
|
|
1212
|
-
return Array.from(new Set((await this.sourcePaths()).map((s) =>
|
|
279
|
+
return Array.from(new Set((await this.sourcePaths()).map((s) => dirname(s.path))));
|
|
1213
280
|
}
|
|
1214
281
|
/**
|
|
1215
282
|
* L'état du disque : les anciens fichiers, et les dossiers `conversations/`.
|
|
@@ -1220,7 +287,7 @@ ${new Error("trace").stack}`
|
|
|
1220
287
|
*/
|
|
1221
288
|
async diskFingerprint() {
|
|
1222
289
|
const paths = (await this.sourcePaths()).map((s) => s.path);
|
|
1223
|
-
const dirs = Array.from(new Set(paths.map((path) => conversationDir(
|
|
290
|
+
const dirs = Array.from(new Set(paths.map((path) => conversationDir(dirname(path)))));
|
|
1224
291
|
return fingerprintFiles([...paths, ...dirs]);
|
|
1225
292
|
}
|
|
1226
293
|
async load() {
|
|
@@ -1295,7 +362,7 @@ ${new Error("trace").stack}`
|
|
|
1295
362
|
}
|
|
1296
363
|
const now = /* @__PURE__ */ new Date();
|
|
1297
364
|
for (const { path } of aggregates) {
|
|
1298
|
-
await
|
|
365
|
+
await rename(path, join(dirname(path), migratedAggregateName(now)));
|
|
1299
366
|
forgetFile(path);
|
|
1300
367
|
console.log(`[conversation-store] ${path} converti en un fichier par conversation.`);
|
|
1301
368
|
}
|
|
@@ -1323,18 +390,18 @@ ${new Error("trace").stack}`
|
|
|
1323
390
|
unreadable.set(root, bucket);
|
|
1324
391
|
};
|
|
1325
392
|
const sources = await this.sourcePaths();
|
|
1326
|
-
const legacyRoots = new Set(sources.filter((s) => s.legacy).map((s) =>
|
|
393
|
+
const legacyRoots = new Set(sources.filter((s) => s.legacy).map((s) => dirname(s.path)));
|
|
1327
394
|
const inheritedProject = legacyRoots.size > 0 ? await localProjectId() : null;
|
|
1328
395
|
for (const { path, legacy } of sources) {
|
|
1329
396
|
let data;
|
|
1330
397
|
try {
|
|
1331
|
-
data = JSON.parse(await
|
|
398
|
+
data = JSON.parse(await readFile(path, "utf-8"));
|
|
1332
399
|
} catch {
|
|
1333
400
|
continue;
|
|
1334
401
|
}
|
|
1335
402
|
aggregates.push({ path });
|
|
1336
403
|
const inherited = legacy ? inheritedProject : null;
|
|
1337
|
-
const root =
|
|
404
|
+
const root = dirname(path);
|
|
1338
405
|
await rememberFile(path);
|
|
1339
406
|
for (const item of data) {
|
|
1340
407
|
try {
|
|
@@ -1357,14 +424,14 @@ ${new Error("trace").stack}`
|
|
|
1357
424
|
continue;
|
|
1358
425
|
}
|
|
1359
426
|
for (const name of names) {
|
|
1360
|
-
const path =
|
|
427
|
+
const path = join(dir, name);
|
|
1361
428
|
const stamp = await fileStamp(path);
|
|
1362
429
|
if (!stamp) continue;
|
|
1363
430
|
let entry = this.parsed.get(path);
|
|
1364
431
|
if (!entry || entry.stamp !== stamp) {
|
|
1365
432
|
let item2;
|
|
1366
433
|
try {
|
|
1367
|
-
item2 = JSON.parse(await
|
|
434
|
+
item2 = JSON.parse(await readFile(path, "utf-8"));
|
|
1368
435
|
} catch {
|
|
1369
436
|
continue;
|
|
1370
437
|
}
|
|
@@ -1479,7 +546,7 @@ ${new Error("trace").stack}`
|
|
|
1479
546
|
if (previous && previous !== path) removed.push(previous);
|
|
1480
547
|
}
|
|
1481
548
|
for (const path of removed) {
|
|
1482
|
-
await
|
|
549
|
+
await rm(path, { force: true });
|
|
1483
550
|
forgetFile(path);
|
|
1484
551
|
this.parsed.delete(path);
|
|
1485
552
|
}
|
|
@@ -1494,7 +561,7 @@ ${new Error("trace").stack}`
|
|
|
1494
561
|
async rootFor(conv) {
|
|
1495
562
|
const known = this.roots.get(conv.id) ?? null;
|
|
1496
563
|
if (conv.projectId) return rootOfProjectAmong(conv.projectId, known);
|
|
1497
|
-
return known ??
|
|
564
|
+
return known ?? dirname(await keyedFilePath(FILENAME));
|
|
1498
565
|
}
|
|
1499
566
|
/**
|
|
1500
567
|
* Le projet d'une conversation, organisation comprise.
|
|
@@ -1569,7 +636,7 @@ ${new Error("trace").stack}`
|
|
|
1569
636
|
await this.load();
|
|
1570
637
|
const now = Date.now();
|
|
1571
638
|
const conv = {
|
|
1572
|
-
id:
|
|
639
|
+
id: randomUUID(),
|
|
1573
640
|
projectId,
|
|
1574
641
|
title: title ?? "New conversation",
|
|
1575
642
|
messages: [],
|
|
@@ -1746,13 +813,13 @@ ${new Error("trace").stack}`
|
|
|
1746
813
|
var conversationStore = new ConversationStore();
|
|
1747
814
|
|
|
1748
815
|
// ../server/src/services/workspace-cwd.ts
|
|
1749
|
-
import { isAbsolute
|
|
816
|
+
import { isAbsolute, join as join2, dirname as dirname2 } from "path";
|
|
1750
817
|
async function projectRoot(ref) {
|
|
1751
818
|
const project = await projectStore.get(ref);
|
|
1752
819
|
if (!project) return null;
|
|
1753
820
|
if (!project.orgId) {
|
|
1754
821
|
const root = vaultFor(project).root;
|
|
1755
|
-
return root === localVaultRoot() ? null :
|
|
822
|
+
return root === localVaultRoot() ? null : dirname2(root);
|
|
1756
823
|
}
|
|
1757
824
|
return projectPathStore.get(project);
|
|
1758
825
|
}
|
|
@@ -1777,11 +844,11 @@ async function resolveWorkspaceCwd(target) {
|
|
|
1777
844
|
}
|
|
1778
845
|
function anchor(base, raw) {
|
|
1779
846
|
if (!raw || raw === ".") return base;
|
|
1780
|
-
if (raw.startsWith("~") || raw.startsWith("$") ||
|
|
1781
|
-
return
|
|
847
|
+
if (raw.startsWith("~") || raw.startsWith("$") || isAbsolute(raw)) return raw;
|
|
848
|
+
return join2(base, raw);
|
|
1782
849
|
}
|
|
1783
850
|
function localVaultRootParent() {
|
|
1784
|
-
return
|
|
851
|
+
return dirname2(localVaultRoot());
|
|
1785
852
|
}
|
|
1786
853
|
async function resolveAgentCwd(target) {
|
|
1787
854
|
if (target.serviceId) {
|
|
@@ -1809,8 +876,8 @@ async function resolveConversationCwd(conversationId) {
|
|
|
1809
876
|
}
|
|
1810
877
|
|
|
1811
878
|
// ../server/src/services/turn-events.ts
|
|
1812
|
-
import { EventEmitter as
|
|
1813
|
-
var bus = new
|
|
879
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
880
|
+
var bus = new EventEmitter2();
|
|
1814
881
|
bus.setMaxListeners(0);
|
|
1815
882
|
function emitTurnFinished(conversationId) {
|
|
1816
883
|
bus.emit("turn-finished", conversationId);
|
|
@@ -1823,11 +890,11 @@ function onTurnFinished(listener) {
|
|
|
1823
890
|
}
|
|
1824
891
|
|
|
1825
892
|
// ../server/src/services/claude-transcript-sync.ts
|
|
1826
|
-
import { readdir as readdir2, access, readFile as
|
|
893
|
+
import { readdir as readdir2, access, readFile as readFile2 } from "fs/promises";
|
|
1827
894
|
import { watch } from "fs";
|
|
1828
895
|
import { homedir } from "os";
|
|
1829
|
-
import { join as
|
|
1830
|
-
var PROJECTS_DIR =
|
|
896
|
+
import { join as join3 } from "path";
|
|
897
|
+
var PROJECTS_DIR = join3(homedir(), ".claude", "projects");
|
|
1831
898
|
var DEFAULT_TITLES = /* @__PURE__ */ new Set(["", "New conversation", "Nouvelle conversation"]);
|
|
1832
899
|
async function findClaudeSessionFile(sessionId) {
|
|
1833
900
|
let entries;
|
|
@@ -1838,7 +905,7 @@ async function findClaudeSessionFile(sessionId) {
|
|
|
1838
905
|
}
|
|
1839
906
|
const filename = `${sessionId}.jsonl`;
|
|
1840
907
|
for (const dir of entries) {
|
|
1841
|
-
const candidate =
|
|
908
|
+
const candidate = join3(PROJECTS_DIR, dir, filename);
|
|
1842
909
|
try {
|
|
1843
910
|
await access(candidate);
|
|
1844
911
|
return candidate;
|
|
@@ -1853,7 +920,7 @@ async function claudeSessionExists(sessionId) {
|
|
|
1853
920
|
async function readClaudeTranscript(sessionId) {
|
|
1854
921
|
const path = await findClaudeSessionFile(sessionId);
|
|
1855
922
|
if (!path) return { found: false, path: null, entries: [] };
|
|
1856
|
-
const raw = await
|
|
923
|
+
const raw = await readFile2(path, "utf-8");
|
|
1857
924
|
const entries = [];
|
|
1858
925
|
for (const line of raw.split("\n")) {
|
|
1859
926
|
if (!line.trim()) continue;
|
|
@@ -2028,7 +1095,7 @@ async function startClaudeTranscriptWatcher() {
|
|
|
2028
1095
|
}
|
|
2029
1096
|
for (const dir of topLevel) {
|
|
2030
1097
|
try {
|
|
2031
|
-
const w = watch(
|
|
1098
|
+
const w = watch(join3(PROJECTS_DIR, dir), (_event, filename) => {
|
|
2032
1099
|
handleFsEvent(filename ? filename.toString() : null);
|
|
2033
1100
|
});
|
|
2034
1101
|
w.on("error", () => {
|
|
@@ -2053,7 +1120,7 @@ function stopClaudeTranscriptWatcher() {
|
|
|
2053
1120
|
|
|
2054
1121
|
// ../server/src/services/codex-transcript-sync.ts
|
|
2055
1122
|
import { watch as watch2 } from "fs";
|
|
2056
|
-
import { dirname as
|
|
1123
|
+
import { dirname as dirname3 } from "path";
|
|
2057
1124
|
var announced = /* @__PURE__ */ new Map();
|
|
2058
1125
|
function forgetCodexTurns(conversationId) {
|
|
2059
1126
|
announced.delete(conversationId);
|
|
@@ -2107,7 +1174,7 @@ function startCodexTranscriptWatcher() {
|
|
|
2107
1174
|
const db = findThreadDb();
|
|
2108
1175
|
if (!db) return;
|
|
2109
1176
|
try {
|
|
2110
|
-
watcher = watch2(
|
|
1177
|
+
watcher = watch2(dirname3(db), (_event, filename) => {
|
|
2111
1178
|
if (!filename || !/^thread_history_\d+\.sqlite/.test(filename.toString())) return;
|
|
2112
1179
|
if (debounce) clearTimeout(debounce);
|
|
2113
1180
|
debounce = setTimeout(() => {
|
|
@@ -2220,6 +1287,100 @@ async function findLivePty(sessionId) {
|
|
|
2220
1287
|
return null;
|
|
2221
1288
|
}
|
|
2222
1289
|
|
|
1290
|
+
// ../server/src/services/pty-input.ts
|
|
1291
|
+
var PTY_SUBMIT_DELAY_MS = 120;
|
|
1292
|
+
function flattenForPty(message) {
|
|
1293
|
+
return message.split(/\n{2,}/).map((block) => block.replace(/\s*\n\s*/g, " ").trim()).filter(Boolean).join(" \xB7 ").replace(/\s{2,}/g, " ").trim();
|
|
1294
|
+
}
|
|
1295
|
+
async function submitToPty(write, message) {
|
|
1296
|
+
await write(flattenForPty(message));
|
|
1297
|
+
await new Promise((r) => setTimeout(r, PTY_SUBMIT_DELAY_MS));
|
|
1298
|
+
await write("\r");
|
|
1299
|
+
}
|
|
1300
|
+
var PASTE_START = "\x1B[200~";
|
|
1301
|
+
var PASTE_END = "\x1B[201~";
|
|
1302
|
+
async function pasteToPty(write, text, options = {}) {
|
|
1303
|
+
if (options.lead) await write(`${options.lead} `);
|
|
1304
|
+
await write(`${PASTE_START}${text}${PASTE_END}`);
|
|
1305
|
+
const delay = Math.min(PTY_SUBMIT_DELAY_MS + Math.ceil(text.length / 100), 3e3);
|
|
1306
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1307
|
+
await write("\r");
|
|
1308
|
+
}
|
|
1309
|
+
var PTY_INPUT_MIN_MS = { "claude-code": 1200, codex: 500 };
|
|
1310
|
+
var PTY_INPUT_QUIET_MS = 400;
|
|
1311
|
+
var PTY_INPUT_MAX_MS = 8e3;
|
|
1312
|
+
var PTY_MCP_READY_MAX_MS = 15e3;
|
|
1313
|
+
var PTY_QUIET_AFTER_MCP_MAX_MS = 3e3;
|
|
1314
|
+
async function waitForPtyInput(opts) {
|
|
1315
|
+
const { agentId, spawnId, mcpReadyId, runner, signal } = opts;
|
|
1316
|
+
if (signal?.aborted) return { status: "aborted" };
|
|
1317
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-46C4NIQ2.js");
|
|
1318
|
+
const { waitForMcpReady } = await import("./mcp-ready-TAPBAOZY.js");
|
|
1319
|
+
return new Promise((resolve) => {
|
|
1320
|
+
const startedAt = Date.now();
|
|
1321
|
+
const floor = PTY_INPUT_MIN_MS[runner];
|
|
1322
|
+
const inner = new AbortController();
|
|
1323
|
+
let done = false;
|
|
1324
|
+
let mcpReady;
|
|
1325
|
+
let quietArmed = !mcpReadyId;
|
|
1326
|
+
let quietTimer = null;
|
|
1327
|
+
let maxTimer = null;
|
|
1328
|
+
let unsub = null;
|
|
1329
|
+
const finish = (status) => {
|
|
1330
|
+
if (done) return;
|
|
1331
|
+
done = true;
|
|
1332
|
+
if (quietTimer) clearTimeout(quietTimer);
|
|
1333
|
+
if (maxTimer) clearTimeout(maxTimer);
|
|
1334
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1335
|
+
inner.abort();
|
|
1336
|
+
unsub?.();
|
|
1337
|
+
resolve(mcpReadyId ? { status, mcpReady: mcpReady ?? false } : { status });
|
|
1338
|
+
};
|
|
1339
|
+
const onAbort = () => finish("aborted");
|
|
1340
|
+
const scheduleQuiet = () => {
|
|
1341
|
+
if (!quietArmed || done) return;
|
|
1342
|
+
if (quietTimer) clearTimeout(quietTimer);
|
|
1343
|
+
const wait = Math.max(PTY_INPUT_QUIET_MS, floor - (Date.now() - startedAt));
|
|
1344
|
+
quietTimer = setTimeout(() => finish("ready"), wait);
|
|
1345
|
+
};
|
|
1346
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1347
|
+
try {
|
|
1348
|
+
unsub = agentManager2.agentSubscribe(
|
|
1349
|
+
agentId,
|
|
1350
|
+
"ai.stream",
|
|
1351
|
+
{ spawnId, fromOffset: 0 },
|
|
1352
|
+
(data) => {
|
|
1353
|
+
const chunk = data.chunk;
|
|
1354
|
+
if (chunk?.type === "exit") {
|
|
1355
|
+
finish("exited");
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
scheduleQuiet();
|
|
1359
|
+
},
|
|
1360
|
+
() => {
|
|
1361
|
+
}
|
|
1362
|
+
);
|
|
1363
|
+
} catch {
|
|
1364
|
+
}
|
|
1365
|
+
if (done) {
|
|
1366
|
+
unsub?.();
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (mcpReadyId) {
|
|
1370
|
+
void waitForMcpReady(mcpReadyId, PTY_MCP_READY_MAX_MS, inner.signal).then((ready) => {
|
|
1371
|
+
if (done) return;
|
|
1372
|
+
mcpReady = ready;
|
|
1373
|
+
quietArmed = true;
|
|
1374
|
+
scheduleQuiet();
|
|
1375
|
+
maxTimer = setTimeout(() => finish("ready"), PTY_QUIET_AFTER_MCP_MAX_MS);
|
|
1376
|
+
});
|
|
1377
|
+
} else {
|
|
1378
|
+
maxTimer = setTimeout(() => finish("ready"), PTY_INPUT_MAX_MS);
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
var PASTED_TASK_LEAD = "Here is your task. Follow the instructions below:";
|
|
1383
|
+
|
|
2223
1384
|
// ../server/src/services/active-session-registry.ts
|
|
2224
1385
|
var MAX_BUFFER = 5e3;
|
|
2225
1386
|
var PERSIST_DEBOUNCE_MS = 100;
|
|
@@ -2637,10 +1798,13 @@ function companionAgentId(livePtyAgentId, lastSessionAgentId) {
|
|
|
2637
1798
|
function trace(conversationId, reason) {
|
|
2638
1799
|
console.log(`[companion] ${conversationId.slice(0, 8)} \u2014 ${reason}`);
|
|
2639
1800
|
}
|
|
1801
|
+
function companionApplies(conv) {
|
|
1802
|
+
return !conv.isCompanion && conv.workflowType !== "scenario";
|
|
1803
|
+
}
|
|
2640
1804
|
async function runCompanionForTurn(conversationId) {
|
|
2641
1805
|
const conv = await conversationStore.get(conversationId);
|
|
2642
1806
|
if (!conv) return trace(conversationId, "conversation introuvable");
|
|
2643
|
-
if (conv
|
|
1807
|
+
if (!companionApplies(conv)) return;
|
|
2644
1808
|
if (!conv.companionEnabled) return;
|
|
2645
1809
|
const stopsAtStart = stops.get(conversationId) ?? 0;
|
|
2646
1810
|
const stopped = () => (stops.get(conversationId) ?? 0) !== stopsAtStart;
|
|
@@ -2649,7 +1813,7 @@ async function runCompanionForTurn(conversationId) {
|
|
|
2649
1813
|
if (!hasNew) {
|
|
2650
1814
|
return skip(conversationId, "nothingNew", `rien de neuf \xE0 relire (${messageCount} messages)`);
|
|
2651
1815
|
}
|
|
2652
|
-
const { startCliSession } = await import("./chat-
|
|
1816
|
+
const { startCliSession } = await import("./chat-CMNDMXIP.js");
|
|
2653
1817
|
const companionConv = await ensureCompanionConversation(conv);
|
|
2654
1818
|
const exchange = formatExchange(conv.messages, from);
|
|
2655
1819
|
const baseSha = await ensureCompanionBase(conv);
|
|
@@ -2700,11 +1864,7 @@ async function runCompanionForTurn(conversationId) {
|
|
|
2700
1864
|
endThinking();
|
|
2701
1865
|
}
|
|
2702
1866
|
}
|
|
2703
|
-
var
|
|
2704
|
-
function flattenForPty(message) {
|
|
2705
|
-
return message.split(/\n{2,}/).map((block) => block.replace(/\s*\n\s*/g, " ").trim()).filter(Boolean).join(" \xB7 ").replace(/\s{2,}/g, " ").trim();
|
|
2706
|
-
}
|
|
2707
|
-
var bus2 = new EventEmitter4();
|
|
1867
|
+
var bus2 = new EventEmitter3();
|
|
2708
1868
|
bus2.setMaxListeners(0);
|
|
2709
1869
|
var thinking = /* @__PURE__ */ new Map();
|
|
2710
1870
|
function companionThinkingIds() {
|
|
@@ -2768,7 +1928,7 @@ async function deliverToMainConversation(conv, message) {
|
|
|
2768
1928
|
async function attemptDelivery(conv, message) {
|
|
2769
1929
|
if (await writeToLivePty(conv.id, message)) return "pty";
|
|
2770
1930
|
try {
|
|
2771
|
-
const { startCliSession } = await import("./chat-
|
|
1931
|
+
const { startCliSession } = await import("./chat-CMNDMXIP.js");
|
|
2772
1932
|
await startCliSession(conv.runner, conv.id, message, { jwt: await signJwt() });
|
|
2773
1933
|
return "headless";
|
|
2774
1934
|
} catch (err) {
|
|
@@ -2781,9 +1941,7 @@ async function writeToLivePty(conversationId, message) {
|
|
|
2781
1941
|
if (!pty) return false;
|
|
2782
1942
|
const write = (data) => agentManager.agentMutation(pty.agentId, "ai.writePty", { spawnId: pty.spawnId, data });
|
|
2783
1943
|
try {
|
|
2784
|
-
await write
|
|
2785
|
-
await new Promise((r) => setTimeout(r, PTY_SUBMIT_DELAY_MS));
|
|
2786
|
-
await write("\r");
|
|
1944
|
+
await submitToPty(write, message);
|
|
2787
1945
|
return true;
|
|
2788
1946
|
} catch {
|
|
2789
1947
|
return false;
|
|
@@ -2903,7 +2061,7 @@ var capturingBase = /* @__PURE__ */ new Set();
|
|
|
2903
2061
|
var baseTriedOnChange = /* @__PURE__ */ new Set();
|
|
2904
2062
|
async function ensureCompanionBase(conv) {
|
|
2905
2063
|
if (conv.companionBaseSha) return conv.companionBaseSha;
|
|
2906
|
-
if (conv
|
|
2064
|
+
if (!companionApplies(conv) || !conv.companionEnabled || capturingBase.has(conv.id)) return void 0;
|
|
2907
2065
|
capturingBase.add(conv.id);
|
|
2908
2066
|
try {
|
|
2909
2067
|
const agentId = conv.agentId ?? agentManager.getDefaultAgentId();
|
|
@@ -2948,12 +2106,6 @@ export {
|
|
|
2948
2106
|
signJwt,
|
|
2949
2107
|
setMcpClientCheck,
|
|
2950
2108
|
verifyJwt,
|
|
2951
|
-
ensureDataGitignore,
|
|
2952
|
-
conversationSectionStore,
|
|
2953
|
-
holdsRuneyaProject,
|
|
2954
|
-
projectPathStore,
|
|
2955
|
-
LocalProjectRefusedError,
|
|
2956
|
-
projectStore,
|
|
2957
2109
|
CONVERSATIONS_DIRNAME,
|
|
2958
2110
|
conversationStore,
|
|
2959
2111
|
resolveWorkspaceCwd,
|
|
@@ -2973,6 +2125,11 @@ export {
|
|
|
2973
2125
|
noteSpawnConversation,
|
|
2974
2126
|
observePtyChunk,
|
|
2975
2127
|
forgetSpawn,
|
|
2128
|
+
flattenForPty,
|
|
2129
|
+
pasteToPty,
|
|
2130
|
+
PTY_MCP_READY_MAX_MS,
|
|
2131
|
+
waitForPtyInput,
|
|
2132
|
+
PASTED_TASK_LEAD,
|
|
2976
2133
|
activeSessionRegistry,
|
|
2977
2134
|
MAX_CONSECUTIVE_RELAUNCHES,
|
|
2978
2135
|
MAX_EXCHANGE_CHARS,
|
|
@@ -2989,7 +2146,7 @@ export {
|
|
|
2989
2146
|
buildRelaunchMessage,
|
|
2990
2147
|
companionSystemNote,
|
|
2991
2148
|
companionAgentId,
|
|
2992
|
-
|
|
2149
|
+
companionApplies,
|
|
2993
2150
|
companionThinkingIds,
|
|
2994
2151
|
isCompanionThinking,
|
|
2995
2152
|
onCompanionThinkingChanged,
|
|
@@ -3007,4 +2164,4 @@ export {
|
|
|
3007
2164
|
headCommit,
|
|
3008
2165
|
initCompanion
|
|
3009
2166
|
};
|
|
3010
|
-
//# sourceMappingURL=chunk-
|
|
2167
|
+
//# sourceMappingURL=chunk-JNK6H5KH.js.map
|