flyspace-cli 0.1.14 → 0.1.15
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/dist/{chunk-DA7SYYKU.js → chunk-KFT2YP5W.js} +376 -13
- package/dist/cli.js +328 -435
- package/dist/index.js +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
HandleTakenError,
|
|
14
14
|
InvalidTransferError,
|
|
15
15
|
LastAdminError,
|
|
16
|
+
MEMORY_TYPES,
|
|
16
17
|
MessageQueue,
|
|
17
18
|
OutputStyleConflictError,
|
|
18
19
|
SKIP_SUBTYPES,
|
|
@@ -39,12 +40,15 @@ import {
|
|
|
39
40
|
createOutputStyle,
|
|
40
41
|
createSkillFile,
|
|
41
42
|
createWorkspace,
|
|
43
|
+
deleteChannelMemory,
|
|
42
44
|
deleteMembership,
|
|
43
45
|
deleteOutputStyle,
|
|
44
46
|
deleteSkillFile,
|
|
45
47
|
deleteWorkspace,
|
|
48
|
+
disableFeatureFlag,
|
|
46
49
|
displayNameFor,
|
|
47
50
|
docsAccessPromptNote,
|
|
51
|
+
enableFeatureFlag,
|
|
48
52
|
ensureAgentDmChannel,
|
|
49
53
|
ensureGeneralChannel,
|
|
50
54
|
ensureLogicalCwdDir,
|
|
@@ -52,12 +56,15 @@ import {
|
|
|
52
56
|
ensureUsersCached,
|
|
53
57
|
expandPath,
|
|
54
58
|
extractCategories,
|
|
59
|
+
featureFlagIds,
|
|
60
|
+
featureFlags,
|
|
55
61
|
findChannel,
|
|
56
62
|
findChannelByAgentName,
|
|
57
63
|
findChannelById,
|
|
58
64
|
findSkillDetail,
|
|
59
65
|
getAgentPaused,
|
|
60
66
|
getAllowGuestAccess,
|
|
67
|
+
getChannelMemory,
|
|
61
68
|
getChannelRegistry,
|
|
62
69
|
getMembershipByEmail,
|
|
63
70
|
getMembershipByUserId,
|
|
@@ -75,11 +82,17 @@ import {
|
|
|
75
82
|
isCodexModel,
|
|
76
83
|
isCwdUsable,
|
|
77
84
|
isEffort,
|
|
85
|
+
isFeatureFlagId,
|
|
86
|
+
isFeatureVisible,
|
|
78
87
|
isLockedPlan,
|
|
79
88
|
isLogicalCwd,
|
|
89
|
+
isMemoryType,
|
|
90
|
+
isUuid,
|
|
80
91
|
isValidSkillName,
|
|
81
92
|
lastRunsForWorkspace,
|
|
82
93
|
listActiveUsers,
|
|
94
|
+
listChannelMemories,
|
|
95
|
+
listEnabledFeatureFlags,
|
|
83
96
|
listMemberChannels,
|
|
84
97
|
listMemberships,
|
|
85
98
|
listMembershipsByEmail,
|
|
@@ -87,9 +100,11 @@ import {
|
|
|
87
100
|
listPluginSkillDetails,
|
|
88
101
|
listSkillFiles,
|
|
89
102
|
listSkills,
|
|
103
|
+
listThreadMemoryEvents,
|
|
90
104
|
listUsersWithAvatars,
|
|
91
105
|
listWorkspaceSummaries,
|
|
92
106
|
listWorkspaces,
|
|
107
|
+
loadAgentMemory,
|
|
93
108
|
loadAutomations,
|
|
94
109
|
loadAutomationsFile,
|
|
95
110
|
loadBotIdentity,
|
|
@@ -122,6 +137,7 @@ import {
|
|
|
122
137
|
resolveChannelId,
|
|
123
138
|
resolveChannelPrompt,
|
|
124
139
|
resolveCwd,
|
|
140
|
+
resolveFeatureFlags,
|
|
125
141
|
resolveLogicalCwd,
|
|
126
142
|
resolveSelectedOutputStyle,
|
|
127
143
|
resolveUserId,
|
|
@@ -130,9 +146,11 @@ import {
|
|
|
130
146
|
runnerForModel,
|
|
131
147
|
saveAgentProfile,
|
|
132
148
|
saveBotIdentity,
|
|
149
|
+
saveChannelMemory,
|
|
133
150
|
saveDashboardSettings,
|
|
134
151
|
saveWorkspaceBranding,
|
|
135
152
|
saveWorkspacePrompt,
|
|
153
|
+
searchChannelMemories,
|
|
136
154
|
setAgentPaused,
|
|
137
155
|
setAllowGuestAccess,
|
|
138
156
|
setMembershipHandle,
|
|
@@ -145,17 +163,20 @@ import {
|
|
|
145
163
|
syncSlackAvatars,
|
|
146
164
|
transferWorkspaceAdmin,
|
|
147
165
|
tsToIso,
|
|
166
|
+
undoMemoryEvent,
|
|
148
167
|
updateChannelById,
|
|
168
|
+
updateChannelMemory,
|
|
149
169
|
updateChannelsMcpConnections,
|
|
150
170
|
updateMembershipRole,
|
|
151
171
|
updateOutputStyle,
|
|
152
172
|
updateWorkspacePlanStatus,
|
|
153
173
|
usageLimitedModels,
|
|
154
174
|
usageLimitedUntil,
|
|
175
|
+
validateMemoryInput,
|
|
155
176
|
warnOnBrokenChannelCwds,
|
|
156
177
|
workspaceSkillsDir,
|
|
157
178
|
writeSkillFile
|
|
158
|
-
} from "./chunk-
|
|
179
|
+
} from "./chunk-KFT2YP5W.js";
|
|
159
180
|
import {
|
|
160
181
|
firstReplyBody,
|
|
161
182
|
getThread,
|
|
@@ -614,8 +635,8 @@ async function requestHostDb(req) {
|
|
|
614
635
|
}
|
|
615
636
|
var BODYLESS_STATUS = /* @__PURE__ */ new Set([101, 204, 205, 304]);
|
|
616
637
|
function dbFetch(send2) {
|
|
617
|
-
return async (input2,
|
|
618
|
-
const req = new Request(input2,
|
|
638
|
+
return async (input2, init2) => {
|
|
639
|
+
const req = new Request(input2, init2);
|
|
619
640
|
const url = new URL(req.url);
|
|
620
641
|
const method = req.method.toUpperCase();
|
|
621
642
|
const res = await send2({
|
|
@@ -678,11 +699,17 @@ function createHostDbClient(via = "socket") {
|
|
|
678
699
|
});
|
|
679
700
|
}
|
|
680
701
|
|
|
702
|
+
// src/commands/supabase-via-host.ts
|
|
703
|
+
function useHostDbWithoutCredentials() {
|
|
704
|
+
const viaHost = !hasSupabaseCredentials() && !!loadCredentials();
|
|
705
|
+
if (viaHost) setSupabaseClient(createHostDbClient("local-host"));
|
|
706
|
+
return viaHost;
|
|
707
|
+
}
|
|
708
|
+
|
|
681
709
|
// src/commands/start.ts
|
|
682
710
|
async function runStart() {
|
|
683
711
|
loadEnvFiles();
|
|
684
|
-
const viaHost =
|
|
685
|
-
if (viaHost) setSupabaseClient(createHostDbClient("local-host"));
|
|
712
|
+
const viaHost = useHostDbWithoutCredentials();
|
|
686
713
|
await preflight({ supabaseViaHost: viaHost });
|
|
687
714
|
await startServer();
|
|
688
715
|
}
|
|
@@ -1738,7 +1765,7 @@ async function runAuthCmd(args) {
|
|
|
1738
1765
|
}
|
|
1739
1766
|
|
|
1740
1767
|
// src/version.ts
|
|
1741
|
-
var CLI_VERSION = true ? "0.1.
|
|
1768
|
+
var CLI_VERSION = true ? "0.1.15" : null;
|
|
1742
1769
|
function isOlderVersion(a, b) {
|
|
1743
1770
|
const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1744
1771
|
const [pa, pb] = [parse2(a), parse2(b)];
|
|
@@ -1893,9 +1920,9 @@ import { tmpdir } from "os";
|
|
|
1893
1920
|
import { promisify } from "util";
|
|
1894
1921
|
var execAsync = promisify(execFile);
|
|
1895
1922
|
var exec = (cmd, args, opts) => {
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
return
|
|
1923
|
+
const run4 = execAsync(cmd, args, opts);
|
|
1924
|
+
run4.child.stdin?.end();
|
|
1925
|
+
return run4;
|
|
1899
1926
|
};
|
|
1900
1927
|
var BOLD3 = "\x1B[1m";
|
|
1901
1928
|
var DIM3 = "\x1B[2m";
|
|
@@ -2230,7 +2257,7 @@ function npmEnv() {
|
|
|
2230
2257
|
return home ? { ...process.env, PATH: `${join10(home, "node", "bin")}:${process.env.PATH ?? ""}` } : process.env;
|
|
2231
2258
|
}
|
|
2232
2259
|
async function runUpgrade() {
|
|
2233
|
-
const current = "0.1.
|
|
2260
|
+
const current = "0.1.15";
|
|
2234
2261
|
console.log(`Current version: ${current}`);
|
|
2235
2262
|
console.log("Checking for updates...\n");
|
|
2236
2263
|
try {
|
|
@@ -2550,6 +2577,7 @@ async function handleImport(manager) {
|
|
|
2550
2577
|
async function runAutomationCmd(args) {
|
|
2551
2578
|
const subcommand = args[0];
|
|
2552
2579
|
loadEnvFiles();
|
|
2580
|
+
useHostDbWithoutCredentials();
|
|
2553
2581
|
await refreshChannelRegistry();
|
|
2554
2582
|
await refreshAutomationRegistry();
|
|
2555
2583
|
const manager = new AutomationManager();
|
|
@@ -2870,9 +2898,9 @@ async function runLogs(args) {
|
|
|
2870
2898
|
|
|
2871
2899
|
// src/commands/slack.ts
|
|
2872
2900
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2873
|
-
import { existsSync as existsSync26, readFileSync as
|
|
2901
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "fs";
|
|
2874
2902
|
import { resolve as resolve6 } from "path";
|
|
2875
|
-
import { tmpdir as
|
|
2903
|
+
import { tmpdir as tmpdir5, homedir as homedir9 } from "os";
|
|
2876
2904
|
import { input, confirm as confirm4, select, checkbox } from "@inquirer/prompts";
|
|
2877
2905
|
import { WebClient as WebClient3 } from "@slack/web-api";
|
|
2878
2906
|
|
|
@@ -3427,6 +3455,13 @@ function profileFromHost(host, known) {
|
|
|
3427
3455
|
const bare = label2.startsWith("api-") ? label2.slice(4) : label2.startsWith("dev-") ? label2.slice(4) : label2;
|
|
3428
3456
|
return known.includes(bare) ? bare : null;
|
|
3429
3457
|
}
|
|
3458
|
+
function isUnknownWorkspaceHost(host, known) {
|
|
3459
|
+
const name = bareHost(host);
|
|
3460
|
+
if (!name?.endsWith(".flyspace.ai")) return false;
|
|
3461
|
+
const label2 = subdomainLabel(host);
|
|
3462
|
+
if (!label2 || label2 === "dev-admin") return false;
|
|
3463
|
+
return profileFromHost(host, known) === null;
|
|
3464
|
+
}
|
|
3430
3465
|
function workspaceHostname(host, profile) {
|
|
3431
3466
|
const name = bareHost(host);
|
|
3432
3467
|
if (!name || !subdomainLabel(host)) return null;
|
|
@@ -3837,7 +3872,9 @@ function getStripeClient() {
|
|
|
3837
3872
|
|
|
3838
3873
|
// src/store/stripe-trial.ts
|
|
3839
3874
|
var PLAN_INFO = {
|
|
3840
|
-
|
|
3875
|
+
// Single-member: a personal Claude subscription can't be shared (see
|
|
3876
|
+
// members-routes.ts, which blocks invites on this plan).
|
|
3877
|
+
local: { name: "Solo", priceUsd: 29 },
|
|
3841
3878
|
cloud: { name: "Cloud Agent", priceUsd: 49 }
|
|
3842
3879
|
};
|
|
3843
3880
|
function priceIdForPlan(plan) {
|
|
@@ -3862,6 +3899,7 @@ async function startWorkspaceTrial(workspace, plan, ownerEmail) {
|
|
|
3862
3899
|
metadata: { workspace_id: workspace.id, plan }
|
|
3863
3900
|
});
|
|
3864
3901
|
await setWorkspaceStripeSubscription(workspace.id, {
|
|
3902
|
+
plan,
|
|
3865
3903
|
stripeCustomerId: customer.id,
|
|
3866
3904
|
stripeSubscriptionId: subscription.id,
|
|
3867
3905
|
planStatus: "trialing"
|
|
@@ -4400,8 +4438,20 @@ async function deliverPasswordResetLink(email, redirectTo) {
|
|
|
4400
4438
|
// src/dashboard/server.ts
|
|
4401
4439
|
import { existsSync as existsSync25 } from "fs";
|
|
4402
4440
|
import { createServer } from "net";
|
|
4403
|
-
import { join as
|
|
4441
|
+
import { join as join24 } from "path";
|
|
4404
4442
|
import { Hono as Hono34 } from "hono";
|
|
4443
|
+
|
|
4444
|
+
// src/dashboard/sentry.ts
|
|
4445
|
+
import * as Sentry from "@sentry/node";
|
|
4446
|
+
import { HTTPException } from "hono/http-exception";
|
|
4447
|
+
var reportingErrorHandler = (err, c) => {
|
|
4448
|
+
if (err instanceof HTTPException) return err.getResponse();
|
|
4449
|
+
Sentry.captureException(err, { extra: { method: c.req.method, path: c.req.path } });
|
|
4450
|
+
console.error(err);
|
|
4451
|
+
return c.text("Internal Server Error", 500);
|
|
4452
|
+
};
|
|
4453
|
+
|
|
4454
|
+
// src/dashboard/server.ts
|
|
4405
4455
|
import { serve } from "@hono/node-server";
|
|
4406
4456
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4407
4457
|
|
|
@@ -4747,15 +4797,15 @@ async function createDocShare(input2) {
|
|
|
4747
4797
|
if (error2) throw new Error(`createDocShare(${input2.sourcePath}): ${error2.message}`);
|
|
4748
4798
|
return rowToShare(data);
|
|
4749
4799
|
}
|
|
4750
|
-
async function updateDocShareSnapshot(id,
|
|
4800
|
+
async function updateDocShareSnapshot(id, snapshot2, features) {
|
|
4751
4801
|
const { data, error: error2 } = await getSupabaseClient().from("doc_shares").update({
|
|
4752
|
-
title:
|
|
4753
|
-
content:
|
|
4754
|
-
asset_keys:
|
|
4802
|
+
title: snapshot2.title,
|
|
4803
|
+
content: snapshot2.content,
|
|
4804
|
+
asset_keys: snapshot2.assetKeys,
|
|
4755
4805
|
// Re-detected with the snapshot: a re-publish can carry a rewritten
|
|
4756
4806
|
// article, and one written in another language than last time is exactly
|
|
4757
4807
|
// the case a stale value would get wrong.
|
|
4758
|
-
source_lang:
|
|
4808
|
+
source_lang: snapshot2.sourceLang,
|
|
4759
4809
|
audio_enabled: features.audioEnabled,
|
|
4760
4810
|
ask_enabled: features.askEnabled,
|
|
4761
4811
|
social_enabled: features.socialEnabled,
|
|
@@ -5683,9 +5733,9 @@ async function synthesizeWithRetry(script, voice) {
|
|
|
5683
5733
|
}
|
|
5684
5734
|
var renderQueue = Promise.resolve();
|
|
5685
5735
|
function queued(job) {
|
|
5686
|
-
const
|
|
5687
|
-
renderQueue =
|
|
5688
|
-
return
|
|
5736
|
+
const run4 = renderQueue.then(job, job);
|
|
5737
|
+
renderQueue = run4.catch(() => void 0);
|
|
5738
|
+
return run4;
|
|
5689
5739
|
}
|
|
5690
5740
|
function renderArticleAudio(title, content, lang) {
|
|
5691
5741
|
return queued(() => renderArticleAudioNow(title, content, lang));
|
|
@@ -5759,12 +5809,12 @@ function queueShareAudio(share) {
|
|
|
5759
5809
|
}
|
|
5760
5810
|
const current = inFlight2.get(share.id);
|
|
5761
5811
|
if (current?.hash === hash) return current.done;
|
|
5762
|
-
const
|
|
5812
|
+
const run4 = () => runShareAudioJob(share, hash).catch((err) => {
|
|
5763
5813
|
console.error(`share audio job crashed (${share.id}):`, err);
|
|
5764
5814
|
}).finally(() => {
|
|
5765
5815
|
if (inFlight2.get(share.id)?.hash === hash) inFlight2.delete(share.id);
|
|
5766
5816
|
});
|
|
5767
|
-
const done = current ? current.done.then(
|
|
5817
|
+
const done = current ? current.done.then(run4) : run4();
|
|
5768
5818
|
inFlight2.set(share.id, { hash, done });
|
|
5769
5819
|
return done;
|
|
5770
5820
|
}
|
|
@@ -6689,8 +6739,8 @@ function routeCookieWrites(all, label2, writes) {
|
|
|
6689
6739
|
|
|
6690
6740
|
// src/dashboard/supabase-server-client.ts
|
|
6691
6741
|
var REQUEST_TIMEOUT_MS2 = 5e3;
|
|
6692
|
-
function fetchWithTimeout(input2,
|
|
6693
|
-
return fetch(input2, { ...
|
|
6742
|
+
function fetchWithTimeout(input2, init2) {
|
|
6743
|
+
return fetch(input2, { ...init2, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2) });
|
|
6694
6744
|
}
|
|
6695
6745
|
function createRequestSupabaseClient(c) {
|
|
6696
6746
|
const url = process.env["SUPABASE_URL"];
|
|
@@ -6732,7 +6782,7 @@ function createRequestSupabaseClient(c) {
|
|
|
6732
6782
|
}
|
|
6733
6783
|
|
|
6734
6784
|
// src/dashboard/auth-context.ts
|
|
6735
|
-
import { HTTPException } from "hono/http-exception";
|
|
6785
|
+
import { HTTPException as HTTPException2 } from "hono/http-exception";
|
|
6736
6786
|
import { isAuthApiError, isAuthRetryableFetchError } from "@supabase/supabase-js";
|
|
6737
6787
|
function onboardingPending(ctx) {
|
|
6738
6788
|
const pending3 = [];
|
|
@@ -6789,10 +6839,10 @@ async function resolveAuthContextUncached(c) {
|
|
|
6789
6839
|
} = await getUserWithRetry(supabase));
|
|
6790
6840
|
} catch (err) {
|
|
6791
6841
|
if (isAuthRetryableFetchError(err)) {
|
|
6792
|
-
throw new
|
|
6842
|
+
throw new HTTPException2(503, { message: "auth service unreachable" });
|
|
6793
6843
|
}
|
|
6794
6844
|
if (isAuthApiError(err) && err.status === 429) {
|
|
6795
|
-
throw new
|
|
6845
|
+
throw new HTTPException2(503, { message: "auth service rate limited" });
|
|
6796
6846
|
}
|
|
6797
6847
|
return null;
|
|
6798
6848
|
}
|
|
@@ -7201,8 +7251,8 @@ async function offlineAgentIdentities() {
|
|
|
7201
7251
|
}
|
|
7202
7252
|
|
|
7203
7253
|
// src/dashboard/host-api.ts
|
|
7204
|
-
import { existsSync as existsSync20, readFileSync as
|
|
7205
|
-
import { join as
|
|
7254
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, statSync as statSync8 } from "fs";
|
|
7255
|
+
import { join as join22 } from "path";
|
|
7206
7256
|
import sharp3 from "sharp";
|
|
7207
7257
|
|
|
7208
7258
|
// src/dashboard/channels-routes.ts
|
|
@@ -7211,81 +7261,6 @@ import { Hono as Hono9 } from "hono";
|
|
|
7211
7261
|
|
|
7212
7262
|
// src/dashboard/feature-flags-routes.ts
|
|
7213
7263
|
import { Hono as Hono8 } from "hono";
|
|
7214
|
-
|
|
7215
|
-
// src/feature-flags.ts
|
|
7216
|
-
var featureFlags = {
|
|
7217
|
-
"ai-cost": {
|
|
7218
|
-
title: "AI cost",
|
|
7219
|
-
description: "Shows admins what the agent has spent: a Cost section in a channel's AI overview and a per-thread cost in the thread menu. Estimated at API list rates, so on a Claude subscription it will not match your bill.",
|
|
7220
|
-
stage: "beta"
|
|
7221
|
-
},
|
|
7222
|
-
"read-aloud": {
|
|
7223
|
-
title: "Read aloud",
|
|
7224
|
-
description: "Listen to messages, threads and docs as audio. Still rough in places, and each playback uses text-to-speech.",
|
|
7225
|
-
stage: "beta"
|
|
7226
|
-
},
|
|
7227
|
-
excalidraw: {
|
|
7228
|
-
title: "Excalidraw",
|
|
7229
|
-
description: "Create and edit whiteboard drawings (.excalidraw files) in the Docs pane. Existing drawings stay viewable with this off.",
|
|
7230
|
-
stage: "beta"
|
|
7231
|
-
},
|
|
7232
|
-
sandbox: {
|
|
7233
|
-
title: "Sandbox runtime",
|
|
7234
|
-
description: "Run a channel agent in an isolated sandbox without Docker. Channels already on Sandbox keep working with this off.",
|
|
7235
|
-
stage: "beta"
|
|
7236
|
-
},
|
|
7237
|
-
dictation: {
|
|
7238
|
-
title: "Dictation",
|
|
7239
|
-
description: "A mic button in the message composer that turns speech into text as you talk. Each phrase uses speech-to-text.",
|
|
7240
|
-
stage: "dev"
|
|
7241
|
-
},
|
|
7242
|
-
"agent-memory": {
|
|
7243
|
-
title: "Agent memory",
|
|
7244
|
-
description: "Channel memory the agent reads every turn, saved by the agent and picked up from idle threads, with a Memory tab and an undo chip. While on, Claude's built-in auto memory is switched off so the two can be compared; turning this off keeps every memory.",
|
|
7245
|
-
stage: "beta"
|
|
7246
|
-
}
|
|
7247
|
-
};
|
|
7248
|
-
var featureFlagIds = Object.keys(featureFlags);
|
|
7249
|
-
function isFeatureFlagId(id) {
|
|
7250
|
-
return Object.hasOwn(featureFlags, id);
|
|
7251
|
-
}
|
|
7252
|
-
function isFeatureVisible(stage, devHost) {
|
|
7253
|
-
return stage === "beta" || stage === "dev" && devHost;
|
|
7254
|
-
}
|
|
7255
|
-
function resolveFeatureFlags(enabledIds, devHost) {
|
|
7256
|
-
const enabled = new Set(enabledIds);
|
|
7257
|
-
const out = {};
|
|
7258
|
-
for (const id of featureFlagIds) {
|
|
7259
|
-
const stage = featureFlags[id].stage;
|
|
7260
|
-
out[id] = stage === "ga" || isFeatureVisible(stage, devHost) && enabled.has(id);
|
|
7261
|
-
}
|
|
7262
|
-
return out;
|
|
7263
|
-
}
|
|
7264
|
-
|
|
7265
|
-
// src/store/feature-flag-store.ts
|
|
7266
|
-
async function listEnabledFeatureFlags(workspaceId) {
|
|
7267
|
-
const { data, error: error2 } = await getSupabaseClient().from("feature_flags").select("flag_id").eq("workspace_id", workspaceId);
|
|
7268
|
-
if (error2) throw new Error(`listEnabledFeatureFlags(${workspaceId}): ${error2.message}`);
|
|
7269
|
-
return (data ?? []).map((r) => r.flag_id);
|
|
7270
|
-
}
|
|
7271
|
-
async function listEnabledFeatureFlagsBySlug(slug) {
|
|
7272
|
-
const { data, error: error2 } = await getSupabaseClient().from("workspaces").select("id").eq("slug", slug).maybeSingle();
|
|
7273
|
-
if (error2) throw new Error(`listEnabledFeatureFlagsBySlug(${slug}): ${error2.message}`);
|
|
7274
|
-
return data ? listEnabledFeatureFlags(data.id) : [];
|
|
7275
|
-
}
|
|
7276
|
-
async function enableFeatureFlag(workspaceId, flagId, userId) {
|
|
7277
|
-
const { error: error2 } = await getSupabaseClient().from("feature_flags").upsert(
|
|
7278
|
-
{ workspace_id: workspaceId, flag_id: flagId, enabled_by: userId },
|
|
7279
|
-
{ onConflict: "workspace_id,flag_id" }
|
|
7280
|
-
);
|
|
7281
|
-
if (error2) throw new Error(`enableFeatureFlag(${workspaceId}, ${flagId}): ${error2.message}`);
|
|
7282
|
-
}
|
|
7283
|
-
async function disableFeatureFlag(workspaceId, flagId) {
|
|
7284
|
-
const { error: error2 } = await getSupabaseClient().from("feature_flags").delete().eq("workspace_id", workspaceId).eq("flag_id", flagId);
|
|
7285
|
-
if (error2) throw new Error(`disableFeatureFlag(${workspaceId}, ${flagId}): ${error2.message}`);
|
|
7286
|
-
}
|
|
7287
|
-
|
|
7288
|
-
// src/dashboard/feature-flags-routes.ts
|
|
7289
7264
|
async function isFeatureEnabled(c, flagId) {
|
|
7290
7265
|
const auth = c.get("auth");
|
|
7291
7266
|
const enabled = await listEnabledFeatureFlags(auth.workspaceId);
|
|
@@ -7509,14 +7484,14 @@ import {
|
|
|
7509
7484
|
openSync,
|
|
7510
7485
|
readSync,
|
|
7511
7486
|
readdirSync as readdirSync4,
|
|
7512
|
-
readFileSync as
|
|
7487
|
+
readFileSync as readFileSync14,
|
|
7513
7488
|
realpathSync as realpathSync3,
|
|
7514
7489
|
renameSync,
|
|
7515
|
-
rmSync as
|
|
7490
|
+
rmSync as rmSync8,
|
|
7516
7491
|
statSync as statSync5,
|
|
7517
7492
|
writeFileSync as writeFileSync9
|
|
7518
7493
|
} from "fs";
|
|
7519
|
-
import { basename as basename6, dirname as dirname5, extname, join as
|
|
7494
|
+
import { basename as basename6, dirname as dirname5, extname, join as join20, relative, resolve as resolve4, sep } from "path";
|
|
7520
7495
|
import { Hono as Hono10 } from "hono";
|
|
7521
7496
|
import { streamSSE } from "hono/streaming";
|
|
7522
7497
|
|
|
@@ -7800,7 +7775,7 @@ async function readOrQueueDocAudio(input2) {
|
|
|
7800
7775
|
}
|
|
7801
7776
|
|
|
7802
7777
|
// src/dashboard/artifact-export.ts
|
|
7803
|
-
import { readFileSync as
|
|
7778
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
7804
7779
|
|
|
7805
7780
|
// src/dashboard/chat-agent.ts
|
|
7806
7781
|
import { homedir as homedir7 } from "os";
|
|
@@ -8584,234 +8559,128 @@ function readAgentPersona(configDir2, cwd, name) {
|
|
|
8584
8559
|
}
|
|
8585
8560
|
}
|
|
8586
8561
|
|
|
8587
|
-
// src/
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
var
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
function
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8562
|
+
// src/dashboard/memory-extraction-runner.ts
|
|
8563
|
+
import { spawn as spawn4 } from "child_process";
|
|
8564
|
+
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync12, rmSync as rmSync7 } from "fs";
|
|
8565
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
8566
|
+
import { join as join18 } from "path";
|
|
8567
|
+
var CLAUDE_MODEL = "sonnet";
|
|
8568
|
+
var CODEX_MODEL = "gpt-6-astra";
|
|
8569
|
+
var CODEX_EFFORT = "low";
|
|
8570
|
+
var TIMEOUT_MS = 15e4;
|
|
8571
|
+
function buildInvocation(runner, req, dir, auth, baseEnv = process.env) {
|
|
8572
|
+
const env = { ...baseEnv, CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" };
|
|
8573
|
+
if (runner === "codex") {
|
|
8574
|
+
const outputFile = join18(dir, "last-message.txt");
|
|
8575
|
+
return {
|
|
8576
|
+
command: "codex",
|
|
8577
|
+
args: [
|
|
8578
|
+
"exec",
|
|
8579
|
+
"--ephemeral",
|
|
8580
|
+
// No MCP servers, profiles or hooks from the user's config.toml; auth
|
|
8581
|
+
// still comes from CODEX_HOME.
|
|
8582
|
+
"--ignore-user-config",
|
|
8583
|
+
"--skip-git-repo-check",
|
|
8584
|
+
"-s",
|
|
8585
|
+
"read-only",
|
|
8586
|
+
"-m",
|
|
8587
|
+
CODEX_MODEL,
|
|
8588
|
+
"-c",
|
|
8589
|
+
`model_reasoning_effort=${CODEX_EFFORT}`,
|
|
8590
|
+
"--output-last-message",
|
|
8591
|
+
outputFile,
|
|
8592
|
+
// Codex has no separate system prompt flag; `-` reads the prompt from stdin.
|
|
8593
|
+
"-"
|
|
8594
|
+
],
|
|
8595
|
+
stdin: `${req.system}
|
|
8596
|
+
|
|
8597
|
+
---
|
|
8598
|
+
|
|
8599
|
+
${req.user}`,
|
|
8600
|
+
env,
|
|
8601
|
+
outputFile
|
|
8602
|
+
};
|
|
8607
8603
|
}
|
|
8608
|
-
|
|
8609
|
-
|
|
8604
|
+
const pinned = pinnedClaudeConfigDir(auth.claudeConfigDir);
|
|
8605
|
+
if (pinned) env["CLAUDE_CONFIG_DIR"] = pinned;
|
|
8606
|
+
else delete env["CLAUDE_CONFIG_DIR"];
|
|
8607
|
+
if (auth.claudeCodeOauthToken) env["CLAUDE_CODE_OAUTH_TOKEN"] = auth.claudeCodeOauthToken;
|
|
8608
|
+
else delete env["CLAUDE_CODE_OAUTH_TOKEN"];
|
|
8610
8609
|
return {
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8610
|
+
command: "claude",
|
|
8611
|
+
args: [
|
|
8612
|
+
"-p",
|
|
8613
|
+
"--model",
|
|
8614
|
+
CLAUDE_MODEL,
|
|
8615
|
+
"--no-session-persistence",
|
|
8616
|
+
"--tools",
|
|
8617
|
+
"",
|
|
8618
|
+
"--strict-mcp-config",
|
|
8619
|
+
"--setting-sources",
|
|
8620
|
+
"",
|
|
8621
|
+
"--system-prompt",
|
|
8622
|
+
req.system
|
|
8623
|
+
],
|
|
8624
|
+
stdin: req.user,
|
|
8625
|
+
env
|
|
8621
8626
|
};
|
|
8622
8627
|
}
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
const { data, error: error2 } = await getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).eq("name", name).maybeSingle();
|
|
8630
|
-
if (error2) throw new Error(`getChannelMemory(${channelId}, ${name}): ${error2.message}`);
|
|
8631
|
-
return data ? rowToMemory(data) : null;
|
|
8632
|
-
}
|
|
8633
|
-
async function saveChannelMemory(input2, actor) {
|
|
8634
|
-
validateMemoryInput(input2);
|
|
8635
|
-
const supabase = getSupabaseClient();
|
|
8636
|
-
const existing = await getChannelMemory(input2.channelId, input2.name);
|
|
8637
|
-
const fields = {
|
|
8638
|
-
description: input2.description.trim(),
|
|
8639
|
-
type: input2.type,
|
|
8640
|
-
body: input2.body.trim(),
|
|
8641
|
-
source_thread_id: input2.sourceThreadId ?? existing?.sourceThreadId ?? null,
|
|
8642
|
-
created_by: input2.createdBy
|
|
8643
|
-
};
|
|
8644
|
-
if (existing && sameContent(existing, fields)) {
|
|
8645
|
-
return { memory: existing, created: false, changed: false };
|
|
8646
|
-
}
|
|
8647
|
-
const { data, error: error2 } = existing ? await supabase.from("memories").update({ ...fields, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id).select("*").single() : await supabase.from("memories").insert({ ...fields, scope: "channel", channel_id: input2.channelId, name: input2.name }).select("*").single();
|
|
8648
|
-
if (error2)
|
|
8649
|
-
throw new Error(`saveChannelMemory(${input2.channelId}, ${input2.name}): ${error2.message}`);
|
|
8650
|
-
const memory = rowToMemory(data);
|
|
8651
|
-
await logMemoryEvent({
|
|
8652
|
-
memory,
|
|
8653
|
-
action: existing ? "update" : "create",
|
|
8654
|
-
before: existing ? snapshot(existing) : null,
|
|
8655
|
-
after: snapshot(memory),
|
|
8656
|
-
actor,
|
|
8657
|
-
threadId: input2.sourceThreadId ?? null
|
|
8658
|
-
});
|
|
8659
|
-
return { memory, created: !existing, changed: true };
|
|
8660
|
-
}
|
|
8661
|
-
async function updateChannelMemory(channelId, memoryId, patch, actor) {
|
|
8662
|
-
const existing = (await listChannelMemories(channelId)).find((m) => m.id === memoryId);
|
|
8663
|
-
if (!existing) return null;
|
|
8664
|
-
const { memory } = await saveChannelMemory(
|
|
8665
|
-
{
|
|
8666
|
-
channelId,
|
|
8667
|
-
name: existing.name,
|
|
8668
|
-
description: patch.description ?? existing.description,
|
|
8669
|
-
type: patch.type ?? existing.type,
|
|
8670
|
-
body: patch.body ?? existing.body,
|
|
8671
|
-
createdBy: existing.createdBy
|
|
8672
|
-
},
|
|
8673
|
-
actor
|
|
8674
|
-
);
|
|
8675
|
-
return memory;
|
|
8676
|
-
}
|
|
8677
|
-
async function searchChannelMemories(channelId, query) {
|
|
8678
|
-
const words = query.replaceAll(/[,()*%\\]/g, " ").split(/\s+/).filter(Boolean);
|
|
8679
|
-
if (!words.length) return [];
|
|
8680
|
-
let request = getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId);
|
|
8681
|
-
for (const word of words) {
|
|
8682
|
-
request = request.or(`name.ilike.*${word}*,description.ilike.*${word}*,body.ilike.*${word}*`);
|
|
8683
|
-
}
|
|
8684
|
-
const { data, error: error2 } = await request.order("updated_at", { ascending: false }).limit(20);
|
|
8685
|
-
if (error2) throw new Error(`searchChannelMemories(${channelId}): ${error2.message}`);
|
|
8686
|
-
return (data ?? []).map(rowToMemory);
|
|
8687
|
-
}
|
|
8688
|
-
async function deleteChannelMemory(channelId, idOrName, actor, threadId) {
|
|
8689
|
-
const { data, error: error2 } = await getSupabaseClient().from("memories").delete().eq("scope", "channel").eq("channel_id", channelId).eq(isUuid(idOrName) ? "id" : "name", idOrName).select("*");
|
|
8690
|
-
if (error2) throw new Error(`deleteChannelMemory(${channelId}, ${idOrName}): ${error2.message}`);
|
|
8691
|
-
const deleted = (data ?? []).map(rowToMemory);
|
|
8692
|
-
for (const memory of deleted) {
|
|
8693
|
-
await logMemoryEvent({
|
|
8694
|
-
memory,
|
|
8695
|
-
action: "delete",
|
|
8696
|
-
before: snapshot(memory),
|
|
8697
|
-
after: null,
|
|
8698
|
-
actor,
|
|
8699
|
-
threadId: threadId ?? null
|
|
8628
|
+
function run3(inv, cwd) {
|
|
8629
|
+
return new Promise((resolve7) => {
|
|
8630
|
+
const child = spawn4(inv.command, inv.args, {
|
|
8631
|
+
cwd,
|
|
8632
|
+
env: inv.env,
|
|
8633
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
8700
8634
|
});
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
}
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8635
|
+
let stdout = "";
|
|
8636
|
+
let stderr = "";
|
|
8637
|
+
const timer2 = setTimeout(() => child.kill("SIGTERM"), TIMEOUT_MS);
|
|
8638
|
+
child.stdout.on("data", (chunk3) => stdout += chunk3.toString());
|
|
8639
|
+
child.stderr.on("data", (chunk3) => stderr += chunk3.toString());
|
|
8640
|
+
child.on("error", (err) => {
|
|
8641
|
+
clearTimeout(timer2);
|
|
8642
|
+
console.error(`[memory-extraction] ${inv.command} failed to start:`, err.message);
|
|
8643
|
+
resolve7({ ok: false });
|
|
8644
|
+
});
|
|
8645
|
+
child.on("close", (code) => {
|
|
8646
|
+
clearTimeout(timer2);
|
|
8647
|
+
if (code !== 0) {
|
|
8648
|
+
const detail = (stderr || stdout).trim().split("\n").at(-1) ?? "";
|
|
8649
|
+
console.error(`[memory-extraction] ${inv.command} exited ${code}: ${detail.slice(0, 200)}`);
|
|
8650
|
+
resolve7({ ok: false });
|
|
8651
|
+
return;
|
|
8652
|
+
}
|
|
8653
|
+
let raw = stdout;
|
|
8654
|
+
if (inv.outputFile) {
|
|
8655
|
+
try {
|
|
8656
|
+
raw = readFileSync12(inv.outputFile, "utf-8");
|
|
8657
|
+
} catch {
|
|
8658
|
+
raw = "";
|
|
8659
|
+
}
|
|
8660
|
+
}
|
|
8661
|
+
resolve7({ ok: true, raw });
|
|
8662
|
+
});
|
|
8663
|
+
child.stdin.end(inv.stdin);
|
|
8726
8664
|
});
|
|
8727
|
-
if (error2) console.error(`[memory] event log failed for ${e.memory.name}:`, error2.message);
|
|
8728
8665
|
}
|
|
8729
|
-
function
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
undoneAt: row["undone_at"] ?? null
|
|
8742
|
-
};
|
|
8743
|
-
}
|
|
8744
|
-
async function listThreadMemoryEvents(channelId, threadId) {
|
|
8745
|
-
const { data, error: error2 } = await getSupabaseClient().from("memory_events").select("*").eq("channel_id", channelId).eq("thread_id", threadId).order("created_at", { ascending: false }).limit(50);
|
|
8746
|
-
if (error2) throw new Error(`listThreadMemoryEvents(${threadId}): ${error2.message}`);
|
|
8747
|
-
return (data ?? []).map(rowToEvent);
|
|
8748
|
-
}
|
|
8749
|
-
async function undoMemoryEvent(channelId, eventId) {
|
|
8750
|
-
const supabase = getSupabaseClient();
|
|
8751
|
-
const { data, error: error2 } = await supabase.from("memory_events").select("*").eq("id", eventId).eq("channel_id", channelId).maybeSingle();
|
|
8752
|
-
if (error2) throw new Error(`undoMemoryEvent(${eventId}): ${error2.message}`);
|
|
8753
|
-
if (!data) return "not-found";
|
|
8754
|
-
const event = rowToEvent(data);
|
|
8755
|
-
if (event.undoneAt) return "already-undone";
|
|
8756
|
-
const current = await getChannelMemory(channelId, event.name);
|
|
8757
|
-
const unchangedSince = current && event.after ? sameContent(current, event.after) : !current && !event.after;
|
|
8758
|
-
if (!unchangedSince) return "conflict";
|
|
8759
|
-
const claimed = await supabase.from("memory_events").update({ undone_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", eventId).is("undone_at", null).select("id");
|
|
8760
|
-
if (claimed.error) throw new Error(`undoMemoryEvent(${eventId}): ${claimed.error.message}`);
|
|
8761
|
-
if (!(claimed.data ?? []).length) return "already-undone";
|
|
8762
|
-
const before = event.before;
|
|
8763
|
-
const restore = before ? {
|
|
8764
|
-
description: before.description,
|
|
8765
|
-
type: before.type,
|
|
8766
|
-
body: before.body,
|
|
8767
|
-
source_thread_id: before.sourceThreadId,
|
|
8768
|
-
created_by: before.createdBy
|
|
8769
|
-
} : null;
|
|
8770
|
-
const { error: writeError } = !restore ? await supabase.from("memories").delete().eq("id", current.id) : current ? await supabase.from("memories").update({ ...restore, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", current.id) : await supabase.from("memories").insert({ ...restore, scope: "channel", channel_id: channelId, name: event.name });
|
|
8771
|
-
if (writeError) {
|
|
8772
|
-
await supabase.from("memory_events").update({ undone_at: null }).eq("id", eventId);
|
|
8773
|
-
throw new Error(`undoMemoryEvent(${eventId}): ${writeError.message}`);
|
|
8774
|
-
}
|
|
8775
|
-
return "undone";
|
|
8776
|
-
}
|
|
8777
|
-
function isUuid(value) {
|
|
8778
|
-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
8779
|
-
}
|
|
8780
|
-
function formatMemoryPrompt(channelId, memories) {
|
|
8781
|
-
const lines = [];
|
|
8782
|
-
let used = 0;
|
|
8783
|
-
let omitted = 0;
|
|
8784
|
-
for (const m of memories) {
|
|
8785
|
-
const line = `- \`${m.name}\` (${m.type}): ${m.description}`;
|
|
8786
|
-
if (used + line.length > MEMORY_INDEX_MAX_CHARS) {
|
|
8787
|
-
omitted++;
|
|
8788
|
-
continue;
|
|
8789
|
-
}
|
|
8790
|
-
lines.push(line);
|
|
8791
|
-
used += line.length + 1;
|
|
8666
|
+
async function completeMemoryExtraction(req) {
|
|
8667
|
+
const runner = runnerForModel(loadDashboardSettings().agent);
|
|
8668
|
+
const config = resolveChannelConfig(req.channelId);
|
|
8669
|
+
const dir = mkdtempSync2(join18(tmpdir3(), "flyspace-memory-"));
|
|
8670
|
+
try {
|
|
8671
|
+
const inv = buildInvocation(runner, req, dir, {
|
|
8672
|
+
...config.claudeConfigDir ? { claudeConfigDir: config.claudeConfigDir } : {},
|
|
8673
|
+
...config.claudeCodeOauthToken ? { claudeCodeOauthToken: config.claudeCodeOauthToken } : {}
|
|
8674
|
+
});
|
|
8675
|
+
return await run3(inv, dir);
|
|
8676
|
+
} finally {
|
|
8677
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
8792
8678
|
}
|
|
8793
|
-
const index = lines.length ? lines.join("\n") + (omitted ? `
|
|
8794
|
-
- (${omitted} older memories not listed; find them with \`memory search\`)` : "") : "(none yet)";
|
|
8795
|
-
return [
|
|
8796
|
-
`## Channel memory`,
|
|
8797
|
-
``,
|
|
8798
|
-
`This channel has a persistent memory shared by every thread in it and by every agent runner. Use it instead of any built-in memory directory of your own CLI.`,
|
|
8799
|
-
``,
|
|
8800
|
-
`- Read a memory: \`flyspace memory get --channel ${channelId} <name>\``,
|
|
8801
|
-
`- Search: \`flyspace memory search --channel ${channelId} <words>\``,
|
|
8802
|
-
`- Save or overwrite: \`flyspace memory save --channel ${channelId} --name <kebab-slug> --type <${MEMORY_TYPES.join("|")}> --description "<one line>" --body "<text>" [--thread <thread-id>]\``,
|
|
8803
|
-
`- Delete one that turned out wrong: \`flyspace memory delete --channel ${channelId} <name> [--thread <thread-id>]\``,
|
|
8804
|
-
``,
|
|
8805
|
-
`Save when someone asks you to remember something, corrects you, or a decision is made that later threads will need. Save what is not derivable from the code, git history or docs: decisions and why, preferences, corrections, where external things live. Do not save one-off details of this conversation. Check the index first and update an existing memory rather than adding a near-duplicate. Write memories in English. Pass \`--thread\` with the thread id from the \`[context: ...]\` header, so the thread shows what changed and it can be undone there.`,
|
|
8806
|
-
``,
|
|
8807
|
-
`Index:`,
|
|
8808
|
-
index
|
|
8809
|
-
].join("\n");
|
|
8810
8679
|
}
|
|
8811
8680
|
|
|
8812
8681
|
// src/dashboard/rollover-summary.ts
|
|
8813
|
-
import { spawn as
|
|
8814
|
-
import { tmpdir as
|
|
8682
|
+
import { spawn as spawn5 } from "child_process";
|
|
8683
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
8815
8684
|
var HAIKU_MODEL = "haiku";
|
|
8816
8685
|
var CJK_PATTERN = /[ -〿-ヿ㐀-䶿一-鿿豈--]/;
|
|
8817
8686
|
function estimateTokens(text) {
|
|
@@ -8870,11 +8739,11 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
|
8870
8739
|
if (claudeConfigDir) env["CLAUDE_CONFIG_DIR"] = claudeConfigDir;
|
|
8871
8740
|
if (claudeCodeOauthToken) env["CLAUDE_CODE_OAUTH_TOKEN"] = claudeCodeOauthToken;
|
|
8872
8741
|
else delete env["CLAUDE_CODE_OAUTH_TOKEN"];
|
|
8873
|
-
const child =
|
|
8742
|
+
const child = spawn5("claude", args, {
|
|
8874
8743
|
// A stateless rewrite touches no files — tmpdir keeps it that way and
|
|
8875
8744
|
// avoids picking up an unrelated project's own CLAUDE.md/AGENTS.md,
|
|
8876
8745
|
// unlike the channel's own cwd the main turn runs in.
|
|
8877
|
-
cwd:
|
|
8746
|
+
cwd: tmpdir4(),
|
|
8878
8747
|
env,
|
|
8879
8748
|
stdio: ["ignore", "pipe", "pipe"]
|
|
8880
8749
|
});
|
|
@@ -9077,7 +8946,7 @@ ${recent.map((m) => `${m.author}: ${m.body}`).join("\n")}`;
|
|
|
9077
8946
|
|
|
9078
8947
|
// src/dashboard/attachment-downloader.ts
|
|
9079
8948
|
import { writeFile } from "fs/promises";
|
|
9080
|
-
import { join as
|
|
8949
|
+
import { join as join19 } from "path";
|
|
9081
8950
|
import { GetObjectCommand as GetObjectCommand4 } from "@aws-sdk/client-s3";
|
|
9082
8951
|
var debug = process.env["DEBUG"] === "true";
|
|
9083
8952
|
var ATTACHMENT_LINK = /\]\(\/api\/attachments\/([^)?\s]+)(?:\?[^)\s]*)?\)/g;
|
|
@@ -9124,7 +8993,7 @@ async function downloadOne(key) {
|
|
|
9124
8993
|
if (bytes.length > sizeLimit) {
|
|
9125
8994
|
return { kind: "skip", skipped: { name, mimetype, reason: "too_large" } };
|
|
9126
8995
|
}
|
|
9127
|
-
const localPath =
|
|
8996
|
+
const localPath = join19(paths.UPLOADS_DIR, lastSegment);
|
|
9128
8997
|
await writeFile(localPath, bytes);
|
|
9129
8998
|
if (debug)
|
|
9130
8999
|
console.log(`[attachment-downloader] saved ${key} -> ${localPath} (${bytes.length} bytes)`);
|
|
@@ -9296,21 +9165,21 @@ var ChatAgent = class {
|
|
|
9296
9165
|
* kill.
|
|
9297
9166
|
*/
|
|
9298
9167
|
async maybeSupersede(msg) {
|
|
9299
|
-
const
|
|
9300
|
-
if (!
|
|
9168
|
+
const run4 = this.activeRuns.get(msg.threadId);
|
|
9169
|
+
if (!run4 || run4.msg.author !== msg.author || run4.msg.replyId === msg.replyId) return;
|
|
9301
9170
|
try {
|
|
9302
|
-
if (!await this.shouldInterrupt(
|
|
9171
|
+
if (!await this.shouldInterrupt(run4.msg.body, msg.body)) return;
|
|
9303
9172
|
} catch (err) {
|
|
9304
9173
|
console.error("[chat-agent] interrupt classifier failed:", err.message);
|
|
9305
9174
|
return;
|
|
9306
9175
|
}
|
|
9307
|
-
if (this.activeRuns.get(msg.threadId) !==
|
|
9176
|
+
if (this.activeRuns.get(msg.threadId) !== run4) return;
|
|
9308
9177
|
console.log(
|
|
9309
9178
|
`[chat-agent] follow-up supersedes in-flight run \u2014 aborting. thread=${msg.threadId}`
|
|
9310
9179
|
);
|
|
9311
|
-
|
|
9180
|
+
run4.superseded = true;
|
|
9312
9181
|
this.supersedingReplies.add(msg.replyId);
|
|
9313
|
-
|
|
9182
|
+
run4.abort.abort();
|
|
9314
9183
|
}
|
|
9315
9184
|
/**
|
|
9316
9185
|
* Picks up messages nobody answered. `handleMessage` is fire-and-forget and
|
|
@@ -9473,6 +9342,11 @@ var ChatAgent = class {
|
|
|
9473
9342
|
return data.filter((r) => r.id !== excludeReplyId).reverse().slice(-limit).map((r) => ({ author: r.author, body: r.body }));
|
|
9474
9343
|
}
|
|
9475
9344
|
/** Whether a reply run is currently in flight for this thread. */
|
|
9345
|
+
/** One idle memory extraction call (see memory-extraction-runner.ts). Not a
|
|
9346
|
+
* turn: no thread, session, queue slot or usage row. */
|
|
9347
|
+
completeMemoryExtraction(req) {
|
|
9348
|
+
return completeMemoryExtraction(req);
|
|
9349
|
+
}
|
|
9476
9350
|
isThinking(threadId) {
|
|
9477
9351
|
return this.activeRuns.has(threadId);
|
|
9478
9352
|
}
|
|
@@ -9508,9 +9382,9 @@ var ChatAgent = class {
|
|
|
9508
9382
|
* continue with `--resume`. Returns whether a run was actually in flight —
|
|
9509
9383
|
* a queued-but-not-started message is not affected. */
|
|
9510
9384
|
cancel(threadId) {
|
|
9511
|
-
const
|
|
9512
|
-
if (!
|
|
9513
|
-
|
|
9385
|
+
const run4 = this.activeRuns.get(threadId);
|
|
9386
|
+
if (!run4) return false;
|
|
9387
|
+
run4.abort.abort();
|
|
9514
9388
|
return true;
|
|
9515
9389
|
}
|
|
9516
9390
|
/**
|
|
@@ -9672,18 +9546,11 @@ var ChatAgent = class {
|
|
|
9672
9546
|
...docsAccessUnverified ? { docsAccessUnverified } : {}
|
|
9673
9547
|
};
|
|
9674
9548
|
}
|
|
9675
|
-
const
|
|
9676
|
-
|
|
9677
|
-
return false;
|
|
9549
|
+
const memory = await loadAgentMemory(owningProfile, thread.channel_id, {
|
|
9550
|
+
readable: docsAccess !== "none"
|
|
9678
9551
|
});
|
|
9679
|
-
if (
|
|
9680
|
-
if (
|
|
9681
|
-
const memoryPrompt = await listChannelMemories(thread.channel_id).then((memories) => formatMemoryPrompt(thread.channel_id, memories)).catch((err) => {
|
|
9682
|
-
console.error("[chat-agent] memory lookup failed:", err.message);
|
|
9683
|
-
return void 0;
|
|
9684
|
-
});
|
|
9685
|
-
if (memoryPrompt) channelConfig = { ...channelConfig, memoryPrompt };
|
|
9686
|
-
}
|
|
9552
|
+
if (memory.enabled) channelConfig = { ...channelConfig, disableAutoMemory: true };
|
|
9553
|
+
if (memory.memoryPrompt) channelConfig = { ...channelConfig, memoryPrompt: memory.memoryPrompt };
|
|
9687
9554
|
const store = this.openSessionStore();
|
|
9688
9555
|
try {
|
|
9689
9556
|
const channelKey = `chat:${thread.channel_id}`;
|
|
@@ -10411,7 +10278,7 @@ async function exportDocAsArtifact(channelId, docPath) {
|
|
|
10411
10278
|
);
|
|
10412
10279
|
const abs = resolveDocPath(channelConfig.cwd, docPath);
|
|
10413
10280
|
if (!abs) throw new Error(`doc not found: ${docPath}`);
|
|
10414
|
-
const content =
|
|
10281
|
+
const content = readFileSync13(abs, "utf8");
|
|
10415
10282
|
const title = extractTitle(abs, "md");
|
|
10416
10283
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10417
10284
|
const prompt = `Publish the document at \`${docPath}\` (already read below) as a Claude Artifact, then record the result as a stub file under this channel's \`docs/artifacts/\` folder (create the folder if it doesn't exist).
|
|
@@ -10668,7 +10535,7 @@ function probeDir(absDir, showHidden) {
|
|
|
10668
10535
|
return false;
|
|
10669
10536
|
}
|
|
10670
10537
|
if (!showHidden && entry.name.startsWith(".")) continue;
|
|
10671
|
-
const abs =
|
|
10538
|
+
const abs = join20(dir, entry.name);
|
|
10672
10539
|
const kind = entryKind(entry, abs);
|
|
10673
10540
|
if (kind === "dir") {
|
|
10674
10541
|
if (SKIP_DIRS.has(entry.name)) hasOther = true;
|
|
@@ -10688,14 +10555,14 @@ function probeDir(absDir, showHidden) {
|
|
|
10688
10555
|
}
|
|
10689
10556
|
function readGitBranch(dir) {
|
|
10690
10557
|
try {
|
|
10691
|
-
let gitDir =
|
|
10558
|
+
let gitDir = join20(dir, ".git");
|
|
10692
10559
|
if (statSync5(gitDir).isFile()) {
|
|
10693
|
-
const pointer =
|
|
10560
|
+
const pointer = readFileSync14(gitDir, "utf8").trim();
|
|
10694
10561
|
const match = pointer.match(/^gitdir:\s*(.+)$/);
|
|
10695
10562
|
if (!match) return void 0;
|
|
10696
10563
|
gitDir = resolve4(dir, match[1]);
|
|
10697
10564
|
}
|
|
10698
|
-
const head =
|
|
10565
|
+
const head = readFileSync14(join20(gitDir, "HEAD"), "utf8").trim();
|
|
10699
10566
|
const branchMatch = head.match(/^ref:\s*refs\/heads\/(.+)$/);
|
|
10700
10567
|
if (branchMatch) return branchMatch[1];
|
|
10701
10568
|
return /^[0-9a-f]{7,40}$/i.test(head) ? head.slice(0, 7) : void 0;
|
|
@@ -10717,14 +10584,14 @@ function listDocLevel(root, relDir = "", sort = "newest", showHidden = false) {
|
|
|
10717
10584
|
for (const entry of entries2) {
|
|
10718
10585
|
if (dirs.length + docs.length >= MAX_ENTRIES) break;
|
|
10719
10586
|
if (!showHidden && entry.name.startsWith(".")) continue;
|
|
10720
|
-
const abs =
|
|
10587
|
+
const abs = join20(absDir, entry.name);
|
|
10721
10588
|
const path = relative(root, abs).split(sep).join("/");
|
|
10722
10589
|
const kind = entryKind(entry, abs);
|
|
10723
10590
|
if (kind === "dir") {
|
|
10724
10591
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
10725
10592
|
const probe3 = probeDir(abs, showHidden);
|
|
10726
10593
|
if (!probe3.hasDocs && probe3.hasOther) continue;
|
|
10727
|
-
const isRepo = existsSync16(
|
|
10594
|
+
const isRepo = existsSync16(join20(abs, ".git"));
|
|
10728
10595
|
dirs.push({
|
|
10729
10596
|
path,
|
|
10730
10597
|
name: entry.name,
|
|
@@ -10760,7 +10627,7 @@ function flatDocs(root, {
|
|
|
10760
10627
|
for (const entry of entries2) {
|
|
10761
10628
|
if (hits.length >= MAX_FLAT_DOCS) return;
|
|
10762
10629
|
if (!showHidden && entry.name.startsWith(".")) continue;
|
|
10763
|
-
const abs =
|
|
10630
|
+
const abs = join20(dir, entry.name);
|
|
10764
10631
|
const kind = entryKind(entry, abs);
|
|
10765
10632
|
if (kind === "dir") {
|
|
10766
10633
|
if (!SKIP_DIRS.has(entry.name)) walk(abs, depth + 1);
|
|
@@ -10840,7 +10707,7 @@ function uniqueName(absDir, name) {
|
|
|
10840
10707
|
let candidate = name;
|
|
10841
10708
|
for (let n = 1; n < 1e3; n++) {
|
|
10842
10709
|
try {
|
|
10843
|
-
statSync5(
|
|
10710
|
+
statSync5(join20(absDir, candidate));
|
|
10844
10711
|
} catch {
|
|
10845
10712
|
return candidate;
|
|
10846
10713
|
}
|
|
@@ -10862,7 +10729,7 @@ function saveUploadedFile(root, relDir, name, data) {
|
|
|
10862
10729
|
if (!(ext in DOC_EXTENSIONS) && !ASSET_LIST_EXTENSIONS.has(ext)) return { status: "bad-type" };
|
|
10863
10730
|
if (data.byteLength > MAX_UPLOAD_BYTES) return { status: "too-large" };
|
|
10864
10731
|
const final = uniqueName(absDir, safe);
|
|
10865
|
-
const abs =
|
|
10732
|
+
const abs = join20(absDir, final);
|
|
10866
10733
|
writeFileSync9(abs, data);
|
|
10867
10734
|
return { status: "ok", path: relative(root, abs).split(sep).join("/"), name: final };
|
|
10868
10735
|
}
|
|
@@ -10876,7 +10743,7 @@ function createDocFolder(root, relDir, name) {
|
|
|
10876
10743
|
}
|
|
10877
10744
|
const safe = sanitizeUploadName(name);
|
|
10878
10745
|
if (!safe) return { status: "bad-name" };
|
|
10879
|
-
const abs =
|
|
10746
|
+
const abs = join20(absDir, safe);
|
|
10880
10747
|
try {
|
|
10881
10748
|
statSync5(abs);
|
|
10882
10749
|
return { status: "exists" };
|
|
@@ -10899,7 +10766,7 @@ function createDocFile(root, relDir, name, content) {
|
|
|
10899
10766
|
if (!(ext in DOC_EXTENSIONS) && !(ext in CODE_EXTENSIONS)) return { status: "bad-name" };
|
|
10900
10767
|
if (SKIP_FILES.has(safe)) return { status: "bad-name" };
|
|
10901
10768
|
if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) return { status: "too-large" };
|
|
10902
|
-
const abs =
|
|
10769
|
+
const abs = join20(absDir, safe);
|
|
10903
10770
|
try {
|
|
10904
10771
|
writeFileSync9(abs, content, { encoding: "utf8", flag: "wx" });
|
|
10905
10772
|
} catch (err) {
|
|
@@ -10937,7 +10804,7 @@ function moveDocEntry(root, from, to) {
|
|
|
10937
10804
|
} catch {
|
|
10938
10805
|
return { status: "bad-target" };
|
|
10939
10806
|
}
|
|
10940
|
-
const finalAbs =
|
|
10807
|
+
const finalAbs = join20(absToDir, name);
|
|
10941
10808
|
if (isDir2 && (finalAbs === absFrom || finalAbs.startsWith(absFrom + sep))) {
|
|
10942
10809
|
return { status: "into-self" };
|
|
10943
10810
|
}
|
|
@@ -10960,7 +10827,7 @@ function deleteDocEntry(root, path) {
|
|
|
10960
10827
|
}
|
|
10961
10828
|
const isDir2 = stat.isDirectory();
|
|
10962
10829
|
if (!isDir2 && !stat.isFile() && !stat.isSymbolicLink()) return { status: "not-found" };
|
|
10963
|
-
|
|
10830
|
+
rmSync8(abs, { recursive: isDir2 });
|
|
10964
10831
|
return {
|
|
10965
10832
|
status: "ok",
|
|
10966
10833
|
path: relative(root, abs).split(sep).join("/"),
|
|
@@ -10999,7 +10866,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
|
10999
10866
|
}
|
|
11000
10867
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
11001
10868
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
11002
|
-
const data =
|
|
10869
|
+
const data = readFileSync14(resolved2.abs);
|
|
11003
10870
|
const headers = { "Content-Type": resolved2.contentType };
|
|
11004
10871
|
if (download) {
|
|
11005
10872
|
const filename = basename6(resolved2.abs).replace(/"/g, "");
|
|
@@ -11019,7 +10886,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
|
11019
10886
|
}
|
|
11020
10887
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
11021
10888
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
11022
|
-
return c.body(new Uint8Array(
|
|
10889
|
+
return c.body(new Uint8Array(readFileSync14(abs)), 200, {
|
|
11023
10890
|
"Content-Type": "text/html; charset=utf-8",
|
|
11024
10891
|
"Content-Security-Policy": HTML_SHARE_CSP,
|
|
11025
10892
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -11204,7 +11071,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
|
11204
11071
|
title: kind ? extractTitle(abs, kind) : basename6(abs),
|
|
11205
11072
|
mtime: stat.mtime.toISOString(),
|
|
11206
11073
|
tooLarge: tooLarge || void 0,
|
|
11207
|
-
content: tooLarge ? "" :
|
|
11074
|
+
content: tooLarge ? "" : readFileSync14(abs, "utf8")
|
|
11208
11075
|
});
|
|
11209
11076
|
});
|
|
11210
11077
|
app.put("/:id/docs/content", async (c) => {
|
|
@@ -11247,7 +11114,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
|
11247
11114
|
channel,
|
|
11248
11115
|
rel,
|
|
11249
11116
|
title: extractTitle(abs, "md"),
|
|
11250
|
-
content:
|
|
11117
|
+
content: readFileSync14(abs, "utf8")
|
|
11251
11118
|
};
|
|
11252
11119
|
};
|
|
11253
11120
|
app.get("/:id/docs/translation/:lang", async (c) => {
|
|
@@ -11470,7 +11337,7 @@ function ownSkills(scope, dir) {
|
|
|
11470
11337
|
return [{ scope, name, description, body, path, plugin: null, mtime }];
|
|
11471
11338
|
});
|
|
11472
11339
|
}
|
|
11473
|
-
function
|
|
11340
|
+
function snapshot(scopes) {
|
|
11474
11341
|
const wants = (scope) => !scopes || scopes.has(scope);
|
|
11475
11342
|
const configDir2 = profileClaudeConfigDir();
|
|
11476
11343
|
const rows = [];
|
|
@@ -11505,7 +11372,7 @@ async function pushSkillScopes(scopes) {
|
|
|
11505
11372
|
if (!mirrorEnabled()) return;
|
|
11506
11373
|
const workspace = getProfile();
|
|
11507
11374
|
const supabase = getSupabaseClient();
|
|
11508
|
-
const rows =
|
|
11375
|
+
const rows = snapshot(scopes ? new Set(scopes) : void 0);
|
|
11509
11376
|
let query = supabase.from("skill_files").select("scope, name, hash").eq("workspace", workspace);
|
|
11510
11377
|
if (scopes) query = query.in("scope", scopes);
|
|
11511
11378
|
const { data, error: error2 } = await query;
|
|
@@ -11789,7 +11656,7 @@ function registerMirroredSkillRoutes(api, agent) {
|
|
|
11789
11656
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11790
11657
|
import { existsSync as existsSync19 } from "fs";
|
|
11791
11658
|
import { homedir as homedir8 } from "os";
|
|
11792
|
-
import { join as
|
|
11659
|
+
import { join as join21, resolve as resolve5 } from "path";
|
|
11793
11660
|
var PROVIDER_LABELS = {
|
|
11794
11661
|
claude: "Claude Code",
|
|
11795
11662
|
codex: "Codex"
|
|
@@ -11807,7 +11674,7 @@ function binaryOnPath(bin) {
|
|
|
11807
11674
|
function claudeAuthAvailable() {
|
|
11808
11675
|
if (profileEnv("CLAUDE_CODE_OAUTH_TOKEN")?.trim()) return true;
|
|
11809
11676
|
const configDir2 = profileClaudeConfigDir() || resolve5(homedir8(), ".claude");
|
|
11810
|
-
if (existsSync19(
|
|
11677
|
+
if (existsSync19(join21(configDir2, ".credentials.json"))) return true;
|
|
11811
11678
|
if (process.platform !== "darwin") return false;
|
|
11812
11679
|
try {
|
|
11813
11680
|
execFileSync3("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
|
|
@@ -11960,7 +11827,7 @@ function registerHostRoutes(api) {
|
|
|
11960
11827
|
const errors = c.req.query("error") === "true" || c.req.query("error") === "1";
|
|
11961
11828
|
const limit = Math.min(Number(c.req.query("limit")) || 200, 2e3);
|
|
11962
11829
|
const filename = errors ? "custie-error.log" : "custie.log";
|
|
11963
|
-
const file =
|
|
11830
|
+
const file = join22(paths.LOG_DIR, filename);
|
|
11964
11831
|
const exists = existsSync20(file);
|
|
11965
11832
|
return c.json({
|
|
11966
11833
|
file,
|
|
@@ -11972,7 +11839,7 @@ function registerHostRoutes(api) {
|
|
|
11972
11839
|
}
|
|
11973
11840
|
function tailFile(file, limit) {
|
|
11974
11841
|
if (!existsSync20(file)) return [];
|
|
11975
|
-
const lines =
|
|
11842
|
+
const lines = readFileSync15(file, "utf-8").split("\n");
|
|
11976
11843
|
if (lines.at(-1) === "") lines.pop();
|
|
11977
11844
|
return lines.slice(-limit);
|
|
11978
11845
|
}
|
|
@@ -13956,6 +13823,10 @@ function createMembersRouter() {
|
|
|
13956
13823
|
if (avatarKey && !avatarKey.startsWith(`${avatarKeyPrefix()}invite-${ctx.userId}-`)) {
|
|
13957
13824
|
return c.json({ error: "unknown avatar" }, 400);
|
|
13958
13825
|
}
|
|
13826
|
+
const { plan } = await getWorkspaceBilling(ctx.workspaceId);
|
|
13827
|
+
if (plan === "local") {
|
|
13828
|
+
return c.json({ error: "The Solo plan is for one person. Upgrade to invite members." }, 403);
|
|
13829
|
+
}
|
|
13959
13830
|
const existing = await getMembershipByEmail(ctx.workspaceId, email);
|
|
13960
13831
|
if (existing) return c.json({ error: "already a member of this workspace" }, 409);
|
|
13961
13832
|
let membership;
|
|
@@ -15153,7 +15024,7 @@ function uniqueChannelName(raw, taken) {
|
|
|
15153
15024
|
// src/dashboard/workspace-greeting.ts
|
|
15154
15025
|
var GROQ_API_URL8 = "https://api.groq.com/openai/v1/chat/completions";
|
|
15155
15026
|
var GREETING_MODEL = "openai/gpt-oss-20b";
|
|
15156
|
-
var
|
|
15027
|
+
var TIMEOUT_MS2 = 6e3;
|
|
15157
15028
|
var MAX_LISTED_CHANNELS = 8;
|
|
15158
15029
|
var GUIDES = [
|
|
15159
15030
|
"Welcome to Flyspace",
|
|
@@ -15203,7 +15074,7 @@ async function groqGreeting(input2, apiKey) {
|
|
|
15203
15074
|
].join("\n");
|
|
15204
15075
|
const res = await fetch(GROQ_API_URL8, {
|
|
15205
15076
|
method: "POST",
|
|
15206
|
-
signal: AbortSignal.timeout(
|
|
15077
|
+
signal: AbortSignal.timeout(TIMEOUT_MS2),
|
|
15207
15078
|
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
15208
15079
|
body: JSON.stringify({
|
|
15209
15080
|
model: GREETING_MODEL,
|
|
@@ -15504,17 +15375,17 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15504
15375
|
const auto = loadEffectiveAutomations();
|
|
15505
15376
|
const runs = /* @__PURE__ */ new Map();
|
|
15506
15377
|
try {
|
|
15507
|
-
for (const [name,
|
|
15508
|
-
runs.set(name, { lastRun:
|
|
15378
|
+
for (const [name, run4] of await lastRunsForWorkspace(getProfile())) {
|
|
15379
|
+
runs.set(name, { lastRun: run4.lastRunAt.toISOString(), lastStatus: run4.lastStatus });
|
|
15509
15380
|
}
|
|
15510
15381
|
} catch {
|
|
15511
15382
|
if (existsSync22(paths.DB_FILE)) {
|
|
15512
15383
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
15513
15384
|
try {
|
|
15514
|
-
for (const
|
|
15515
|
-
runs.set(
|
|
15516
|
-
lastRun:
|
|
15517
|
-
lastStatus:
|
|
15385
|
+
for (const run4 of runStore.listLastRuns()) {
|
|
15386
|
+
runs.set(run4.name, {
|
|
15387
|
+
lastRun: run4.lastRunAt.toISOString(),
|
|
15388
|
+
lastStatus: run4.lastStatus
|
|
15518
15389
|
});
|
|
15519
15390
|
}
|
|
15520
15391
|
} finally {
|
|
@@ -15704,17 +15575,17 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15704
15575
|
}
|
|
15705
15576
|
const threadLinks = await resolveThreadLinks(raw);
|
|
15706
15577
|
const sessions = raw.map((s) => {
|
|
15707
|
-
const
|
|
15708
|
-
const turns =
|
|
15709
|
-
const total =
|
|
15578
|
+
const run4 = latest.get(s.threadTs);
|
|
15579
|
+
const turns = run4?.numTurns ?? 1;
|
|
15580
|
+
const total = run4 ? run4.inputTokens + run4.cacheReadTokens + run4.cacheCreationTokens : void 0;
|
|
15710
15581
|
const link = s.channelId.startsWith("chat:") ? threadLinks.chat.get(s.threadTs) : threadLinks.external.get(`${s.channelId}:${s.threadTs}`);
|
|
15711
15582
|
return {
|
|
15712
15583
|
...s,
|
|
15713
15584
|
channelLabel: channelLabel(s.channelId),
|
|
15714
|
-
model:
|
|
15715
|
-
numTurns:
|
|
15585
|
+
model: run4?.model,
|
|
15586
|
+
numTurns: run4?.numTurns,
|
|
15716
15587
|
contextTokens: total !== void 0 ? Math.round(total / (turns > 0 ? turns : 1)) : void 0,
|
|
15717
|
-
rolledOver:
|
|
15588
|
+
rolledOver: run4?.label === "rollover",
|
|
15718
15589
|
threadTitle: link?.title ?? void 0,
|
|
15719
15590
|
chatChannelId: link?.chatChannelId,
|
|
15720
15591
|
chatThreadId: link?.chatThreadId,
|
|
@@ -15817,16 +15688,16 @@ function relay2(agent) {
|
|
|
15817
15688
|
}
|
|
15818
15689
|
|
|
15819
15690
|
// src/dashboard/agent-bridge.ts
|
|
15820
|
-
import { existsSync as existsSync24, readFileSync as
|
|
15691
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
|
|
15821
15692
|
import dotenv from "dotenv";
|
|
15822
15693
|
import { WebSocket, WebSocketServer } from "ws";
|
|
15823
15694
|
|
|
15824
15695
|
// src/dashboard/host-identity.ts
|
|
15825
15696
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
|
|
15826
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as
|
|
15827
|
-
import { dirname as dirname7, join as
|
|
15697
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
|
|
15698
|
+
import { dirname as dirname7, join as join23 } from "path";
|
|
15828
15699
|
function hostPrivateKeyPath() {
|
|
15829
|
-
return process.env["FLYSPACE_HOST_KEY_PATH"] ||
|
|
15700
|
+
return process.env["FLYSPACE_HOST_KEY_PATH"] || join23(paths.BASE_CONFIG_DIR, "host.key");
|
|
15830
15701
|
}
|
|
15831
15702
|
function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
15832
15703
|
if (!existsSync23(path)) {
|
|
@@ -15834,7 +15705,7 @@ function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
|
15834
15705
|
`No Host identity at ${path}. Run \`pnpm run generate-host-key\` on this machine, then set FLYSPACE_HOST_PUBLIC_KEY on the Data process to the printed public key.`
|
|
15835
15706
|
);
|
|
15836
15707
|
}
|
|
15837
|
-
return createPrivateKey2(
|
|
15708
|
+
return createPrivateKey2(readFileSync16(path, "utf8"));
|
|
15838
15709
|
}
|
|
15839
15710
|
function loadHostPublicKey() {
|
|
15840
15711
|
const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
|
|
@@ -16079,7 +15950,7 @@ async function userEmail(userId) {
|
|
|
16079
15950
|
|
|
16080
15951
|
// src/dashboard/doc-share-publisher.ts
|
|
16081
15952
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
16082
|
-
import { readFileSync as
|
|
15953
|
+
import { readFileSync as readFileSync17, statSync as statSync9 } from "fs";
|
|
16083
15954
|
import { extname as extname2, posix } from "path";
|
|
16084
15955
|
import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
|
|
16085
15956
|
|
|
@@ -16266,7 +16137,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16266
16137
|
}
|
|
16267
16138
|
if (!stat.isFile()) return { status: "not-found" };
|
|
16268
16139
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
16269
|
-
const source =
|
|
16140
|
+
const source = readFileSync17(abs, "utf8");
|
|
16270
16141
|
const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
|
|
16271
16142
|
if (kind === "excalidraw") {
|
|
16272
16143
|
try {
|
|
@@ -16346,7 +16217,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16346
16217
|
let text = null;
|
|
16347
16218
|
try {
|
|
16348
16219
|
const size = statSync9(docAbs).size;
|
|
16349
|
-
if (size <= budget) text =
|
|
16220
|
+
if (size <= budget) text = readFileSync17(docAbs, "utf8");
|
|
16350
16221
|
} catch {
|
|
16351
16222
|
continue;
|
|
16352
16223
|
}
|
|
@@ -16363,7 +16234,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16363
16234
|
let body;
|
|
16364
16235
|
try {
|
|
16365
16236
|
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
16366
|
-
body =
|
|
16237
|
+
body = readFileSync17(resolved2.abs);
|
|
16367
16238
|
} catch {
|
|
16368
16239
|
continue;
|
|
16369
16240
|
}
|
|
@@ -16405,7 +16276,7 @@ async function publishDocShare(input2) {
|
|
|
16405
16276
|
...HTML_FEATURES,
|
|
16406
16277
|
brandingEnabled: resolveFeatures(input2.features, existing).brandingEnabled
|
|
16407
16278
|
} : html ? HTML_FEATURES : resolveFeatures(input2.features, existing);
|
|
16408
|
-
const
|
|
16279
|
+
const snapshot2 = {
|
|
16409
16280
|
...built,
|
|
16410
16281
|
sourceLang: html ? null : await detectSourceLanguage(built.title, built.content)
|
|
16411
16282
|
};
|
|
@@ -16415,8 +16286,8 @@ async function publishDocShare(input2) {
|
|
|
16415
16286
|
const renamed = await setDocShareSlug(existing.id, slug);
|
|
16416
16287
|
if (!renamed) return { status: "not-found" };
|
|
16417
16288
|
}
|
|
16418
|
-
const share2 = await updateDocShareSnapshot(existing.id,
|
|
16419
|
-
const stale = existing.assetKeys.filter((key) => !
|
|
16289
|
+
const share2 = await updateDocShareSnapshot(existing.id, snapshot2, features);
|
|
16290
|
+
const stale = existing.assetKeys.filter((key) => !snapshot2.assetKeys.includes(key));
|
|
16420
16291
|
await deleteShareAssets(stale);
|
|
16421
16292
|
if (!features.audioEnabled && existing.audioEnabled) await dropShareAudio(existing);
|
|
16422
16293
|
void queueShareAudio(share2);
|
|
@@ -16430,7 +16301,7 @@ async function publishDocShare(input2) {
|
|
|
16430
16301
|
channelId: input2.channelId,
|
|
16431
16302
|
sourcePath: input2.sourcePath,
|
|
16432
16303
|
createdBy: input2.createdBy,
|
|
16433
|
-
...
|
|
16304
|
+
...snapshot2,
|
|
16434
16305
|
...features
|
|
16435
16306
|
});
|
|
16436
16307
|
void queueShareAudio(share);
|
|
@@ -16457,9 +16328,12 @@ var METHODS = {
|
|
|
16457
16328
|
thread_replies: ["GET", "HEAD", "POST", "PATCH"],
|
|
16458
16329
|
memberships: ["GET", "HEAD"],
|
|
16459
16330
|
channel_members: ["GET", "HEAD"],
|
|
16460
|
-
channel_docs_access: ["GET", "HEAD"]
|
|
16331
|
+
channel_docs_access: ["GET", "HEAD"],
|
|
16332
|
+
memories: ALL,
|
|
16333
|
+
memory_events: ["GET", "HEAD", "POST", "PATCH"]
|
|
16461
16334
|
};
|
|
16462
16335
|
var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members", "channel_docs_access"]);
|
|
16336
|
+
var CHANNEL_OWNED = /* @__PURE__ */ new Set(["memories", "memory_events"]);
|
|
16463
16337
|
var FORWARDED_HEADERS2 = ["accept", "content-type", "prefer", "range"];
|
|
16464
16338
|
var deny = (message, status = 403) => ({
|
|
16465
16339
|
status,
|
|
@@ -16575,6 +16449,16 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
16575
16449
|
if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16576
16450
|
if (CHANNEL_SCOPED.has(table)) {
|
|
16577
16451
|
params.append("channel_id", inList(channelIds));
|
|
16452
|
+
} else if (CHANNEL_OWNED.has(table)) {
|
|
16453
|
+
if (method === "POST") {
|
|
16454
|
+
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
16455
|
+
if (!ok3) return deny(`Every ${table} row must belong to a channel of the device workspaces.`);
|
|
16456
|
+
}
|
|
16457
|
+
if (method === "PATCH") {
|
|
16458
|
+
const patch = bodyRows(req.body);
|
|
16459
|
+
if (!patch || patch.some((r) => "channel_id" in r)) return deny(`A ${table} row cannot move channels.`);
|
|
16460
|
+
}
|
|
16461
|
+
params.append("channel_id", inList(channelIds));
|
|
16578
16462
|
} else if (table === "threads") {
|
|
16579
16463
|
if (method === "POST") {
|
|
16580
16464
|
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
@@ -16664,6 +16548,7 @@ function localAgentCallbacks(chatAgent) {
|
|
|
16664
16548
|
cancelAgentRun: (threadId) => chatAgent.cancel(threadId),
|
|
16665
16549
|
regenerateAgentRun: (threadId) => chatAgent.regenerate(threadId),
|
|
16666
16550
|
listAgentRuns: () => chatAgent.runningThreadIds(),
|
|
16551
|
+
completeMemoryExtraction: (req) => chatAgent.completeMemoryExtraction(req),
|
|
16667
16552
|
listMissingCwds: () => auditChannelCwds().map((ch) => ch.id),
|
|
16668
16553
|
listRunningProfiles: () => listProfiles().filter(isServiceRunning2),
|
|
16669
16554
|
getAgentIdentities: () => resolveAgentIdentities(),
|
|
@@ -16683,14 +16568,14 @@ function localAgentCallbacks(chatAgent) {
|
|
|
16683
16568
|
}
|
|
16684
16569
|
var HOST_CONFIG_PUSH_INTERVAL_MS = 6e4;
|
|
16685
16570
|
function buildHostConfigSnapshot() {
|
|
16686
|
-
const
|
|
16571
|
+
const snapshot2 = {};
|
|
16687
16572
|
for (const profile of listProfiles()) {
|
|
16688
|
-
|
|
16689
|
-
env: existsSync24(paths.CONFIG_FILE) ? dotenv.parse(
|
|
16573
|
+
snapshot2[profile] = runWithProfile(profile, () => ({
|
|
16574
|
+
env: existsSync24(paths.CONFIG_FILE) ? dotenv.parse(readFileSync18(paths.CONFIG_FILE, "utf-8")) : {},
|
|
16690
16575
|
agentModel: loadDashboardSettings().agent
|
|
16691
16576
|
}));
|
|
16692
16577
|
}
|
|
16693
|
-
return
|
|
16578
|
+
return snapshot2;
|
|
16694
16579
|
}
|
|
16695
16580
|
function startLocalProfileConfigHydration() {
|
|
16696
16581
|
setProfileConfigCache(buildHostConfigSnapshot());
|
|
@@ -16699,6 +16584,7 @@ function startLocalProfileConfigHydration() {
|
|
|
16699
16584
|
}
|
|
16700
16585
|
var CALL_TIMEOUT_MS = 15e3;
|
|
16701
16586
|
var DOCS_CALL_TIMEOUT_MS = 12e4;
|
|
16587
|
+
var MEMORY_EXTRACTION_TIMEOUT_MS = 165e3;
|
|
16702
16588
|
var AUTH_TIMEOUT_MS = 5e3;
|
|
16703
16589
|
var HEARTBEAT_INTERVAL_MS = 15e3;
|
|
16704
16590
|
var HEARTBEAT_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 2.5;
|
|
@@ -16926,6 +16812,9 @@ function remoteAgentCallbacks() {
|
|
|
16926
16812
|
cancelAgentRun: async (threadId) => await callHost("cancelAgentRun", [threadId]) ?? false,
|
|
16927
16813
|
regenerateAgentRun: async (threadId) => await callHost("regenerateAgentRun", [threadId]) ?? false,
|
|
16928
16814
|
listAgentRuns: async () => await callHost("listAgentRuns", []) ?? [],
|
|
16815
|
+
// `null` (no Host, or no answer in time) passes through: the extractor
|
|
16816
|
+
// reads it as a failed call and retries the thread next sweep.
|
|
16817
|
+
completeMemoryExtraction: (req) => callHost("completeMemoryExtraction", [req], MEMORY_EXTRACTION_TIMEOUT_MS),
|
|
16929
16818
|
// Deliberately passes `null` through rather than defaulting to `[]`: with
|
|
16930
16819
|
// no Host connected, claiming every folder is fine would be a guess.
|
|
16931
16820
|
listMissingCwds: () => callHost("listMissingCwds", []),
|
|
@@ -17411,6 +17300,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
17411
17300
|
const resolved2 = requested && known.includes(requested) ? requested : profileFromHost(trustedHost(c), known);
|
|
17412
17301
|
if (!resolved2) {
|
|
17413
17302
|
return c.json({
|
|
17303
|
+
unknownWorkspace: isUnknownWorkspaceHost(trustedHost(c), known),
|
|
17414
17304
|
theme: BRANDING_DEFAULTS.theme,
|
|
17415
17305
|
font: BRANDING_DEFAULTS.font,
|
|
17416
17306
|
displayName: "Flyspace",
|
|
@@ -17731,7 +17621,7 @@ function startHostStatusBroadcast() {
|
|
|
17731
17621
|
|
|
17732
17622
|
// src/dashboard/server.ts
|
|
17733
17623
|
var DEFAULT_DASHBOARD_PORT = 4747;
|
|
17734
|
-
var WEB_DIST =
|
|
17624
|
+
var WEB_DIST = join24(paths.PACKAGE_ROOT, "web", "dist");
|
|
17735
17625
|
var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
17736
17626
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
17737
17627
|
for (let port = start; port < start + attempts; port++) {
|
|
@@ -17781,6 +17671,7 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17781
17671
|
const stopSkillSync = mirrorsSkills ? installSkillSync(listProfiles()) : () => {
|
|
17782
17672
|
};
|
|
17783
17673
|
const app = new Hono34();
|
|
17674
|
+
app.onError(reportingErrorHandler);
|
|
17784
17675
|
app.use("*", async (c, next) => {
|
|
17785
17676
|
await next();
|
|
17786
17677
|
if (c.req.path.startsWith("/assets/")) {
|
|
@@ -17819,14 +17710,14 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17819
17710
|
} else {
|
|
17820
17711
|
app.route("/share", createPublicShareAssetRouter(WEB_DIST));
|
|
17821
17712
|
app.route("/", createAppIconRouter());
|
|
17822
|
-
if (existsSync25(WEB_DIST) || existsSync25(
|
|
17713
|
+
if (existsSync25(WEB_DIST) || existsSync25(join24(paths.PACKAGE_ROOT, "web", "package.json"))) {
|
|
17823
17714
|
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
17824
17715
|
}
|
|
17825
17716
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
17826
|
-
const spaFallback = serveStatic({ path:
|
|
17717
|
+
const spaFallback = serveStatic({ path: join24(WEB_DIST, "index.html") });
|
|
17827
17718
|
app.get("*", async (c, next) => {
|
|
17828
|
-
if (!existsSync25(
|
|
17829
|
-
const isCheckout = existsSync25(
|
|
17719
|
+
if (!existsSync25(join24(WEB_DIST, "index.html"))) {
|
|
17720
|
+
const isCheckout = existsSync25(join24(paths.PACKAGE_ROOT, "web", "package.json"));
|
|
17830
17721
|
return c.text(
|
|
17831
17722
|
isCheckout ? `Workspace UI build missing (${WEB_DIST}).
|
|
17832
17723
|
Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
|
|
@@ -18264,7 +18155,7 @@ async function threadReplies(client2, nameOrId, ts2) {
|
|
|
18264
18155
|
async function postMessage(client2, args) {
|
|
18265
18156
|
const channelArg = getArg2(args, "--channel");
|
|
18266
18157
|
const textFilePath = getArg2(args, "--text-file");
|
|
18267
|
-
const text = textFilePath ?
|
|
18158
|
+
const text = textFilePath ? readFileSync19(textFilePath, "utf8") : getArg2(args, "--text");
|
|
18268
18159
|
const threadTs = getArg2(args, "--thread");
|
|
18269
18160
|
const markdown = args.includes("--markdown");
|
|
18270
18161
|
const broadcast = args.includes("--broadcast") && Boolean(threadTs);
|
|
@@ -18419,7 +18310,7 @@ async function ensureClaudeCodeReady() {
|
|
|
18419
18310
|
}
|
|
18420
18311
|
function getManifestYaml(botName) {
|
|
18421
18312
|
const manifestPath = resolve6(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
|
|
18422
|
-
const raw =
|
|
18313
|
+
const raw = readFileSync19(manifestPath, "utf-8");
|
|
18423
18314
|
return raw.replace(/name: Custie/g, `name: ${botName}`);
|
|
18424
18315
|
}
|
|
18425
18316
|
async function stepPersonalise() {
|
|
@@ -18632,7 +18523,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18632
18523
|
})
|
|
18633
18524
|
));
|
|
18634
18525
|
const DEBUG = process.argv.includes("--debug");
|
|
18635
|
-
const USER_DATA_DIR = resolve6(
|
|
18526
|
+
const USER_DATA_DIR = resolve6(tmpdir5(), "custie-playwright-profile");
|
|
18636
18527
|
let context;
|
|
18637
18528
|
try {
|
|
18638
18529
|
log4("Opening browser \u2014 a Slack login page will appear.\n");
|
|
@@ -18851,7 +18742,7 @@ function copyDefaultPrompt(botName) {
|
|
|
18851
18742
|
if (!existsSync26(paths.PROMPT_FILE)) {
|
|
18852
18743
|
const defaultPrompt = resolve6(paths.PACKAGE_ROOT, "system.default.md");
|
|
18853
18744
|
if (existsSync26(defaultPrompt)) {
|
|
18854
|
-
const content =
|
|
18745
|
+
const content = readFileSync19(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
|
|
18855
18746
|
writeFileSync11(paths.PROMPT_FILE, content);
|
|
18856
18747
|
success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
|
|
18857
18748
|
}
|
|
@@ -19146,6 +19037,7 @@ async function runPost(args) {
|
|
|
19146
19037
|
}
|
|
19147
19038
|
async function runChatCmd(args) {
|
|
19148
19039
|
loadEnvFiles();
|
|
19040
|
+
useHostDbWithoutCredentials();
|
|
19149
19041
|
const subcommand = args[0];
|
|
19150
19042
|
const rest = args.slice(1);
|
|
19151
19043
|
switch (subcommand) {
|
|
@@ -19174,21 +19066,21 @@ async function runChatCmd(args) {
|
|
|
19174
19066
|
}
|
|
19175
19067
|
|
|
19176
19068
|
// src/commands/memory.ts
|
|
19177
|
-
import { readFileSync as
|
|
19069
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
19178
19070
|
|
|
19179
19071
|
// src/store/claude-memory-import.ts
|
|
19180
|
-
import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as
|
|
19072
|
+
import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as readFileSync20 } from "fs";
|
|
19181
19073
|
import { homedir as homedir10 } from "os";
|
|
19182
|
-
import { basename as basename8, join as
|
|
19074
|
+
import { basename as basename8, join as join25 } from "path";
|
|
19183
19075
|
import dotenv2 from "dotenv";
|
|
19184
19076
|
function claudeMemoryDir(cwd, configDir2) {
|
|
19185
|
-
return
|
|
19077
|
+
return join25(configDir2, "projects", cwd.replaceAll(/[^a-zA-Z0-9]/g, "-"), "memory");
|
|
19186
19078
|
}
|
|
19187
19079
|
function profileClaudeConfigDirFor(profile) {
|
|
19188
|
-
const dir = profile === "default" ? paths.BASE_CONFIG_DIR :
|
|
19189
|
-
const file =
|
|
19190
|
-
const env = existsSync27(file) ? dotenv2.parse(
|
|
19191
|
-
return env["CLAUDE_CONFIG_DIR"]?.trim() ||
|
|
19080
|
+
const dir = profile === "default" ? paths.BASE_CONFIG_DIR : join25(paths.PROFILES_DIR, profile);
|
|
19081
|
+
const file = join25(dir, "config.env");
|
|
19082
|
+
const env = existsSync27(file) ? dotenv2.parse(readFileSync20(file)) : {};
|
|
19083
|
+
return env["CLAUDE_CONFIG_DIR"]?.trim() || join25(homedir10(), ".claude");
|
|
19192
19084
|
}
|
|
19193
19085
|
var TYPE_MAP = {
|
|
19194
19086
|
user: "preference",
|
|
@@ -19196,7 +19088,7 @@ var TYPE_MAP = {
|
|
|
19196
19088
|
project: "fact",
|
|
19197
19089
|
reference: "reference"
|
|
19198
19090
|
};
|
|
19199
|
-
var
|
|
19091
|
+
var DESCRIPTION_MAX = 200;
|
|
19200
19092
|
function unquote2(value) {
|
|
19201
19093
|
const v = value.trim();
|
|
19202
19094
|
return /^(["']).*\1$/.test(v) ? v.slice(1, -1) : v;
|
|
@@ -19214,8 +19106,8 @@ function parseClaudeMemory(fileName, text, channelId) {
|
|
|
19214
19106
|
}
|
|
19215
19107
|
const name = slugify(fields["name"] || basename8(fileName, ".md"));
|
|
19216
19108
|
let description = (fields["description"] ?? "").replaceAll(/\s+/g, " ").trim();
|
|
19217
|
-
if (description.length >
|
|
19218
|
-
description = `${description.slice(0,
|
|
19109
|
+
if (description.length > DESCRIPTION_MAX) {
|
|
19110
|
+
description = `${description.slice(0, DESCRIPTION_MAX - 3).trimEnd()}...`;
|
|
19219
19111
|
}
|
|
19220
19112
|
const input2 = {
|
|
19221
19113
|
channelId,
|
|
@@ -19237,7 +19129,7 @@ async function importClaudeMemories(dir, channelId, opts = {}) {
|
|
|
19237
19129
|
const files = readdirSync5(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
19238
19130
|
const results = [];
|
|
19239
19131
|
for (const file of files) {
|
|
19240
|
-
const parsed = parseClaudeMemory(file,
|
|
19132
|
+
const parsed = parseClaudeMemory(file, readFileSync20(join25(dir, file), "utf-8"), channelId);
|
|
19241
19133
|
if ("skip" in parsed) {
|
|
19242
19134
|
results.push({ file, outcome: "skipped", reason: parsed.skip });
|
|
19243
19135
|
continue;
|
|
@@ -19291,6 +19183,7 @@ function indexLine(m) {
|
|
|
19291
19183
|
}
|
|
19292
19184
|
async function runMemoryCmd(args) {
|
|
19293
19185
|
loadEnvFiles();
|
|
19186
|
+
useHostDbWithoutCredentials();
|
|
19294
19187
|
const subcommand = args[0];
|
|
19295
19188
|
const rest = args.slice(1);
|
|
19296
19189
|
if (!subcommand || subcommand === "-h" || subcommand === "--help") {
|
|
@@ -19336,7 +19229,7 @@ ${memory.body}`);
|
|
|
19336
19229
|
);
|
|
19337
19230
|
}
|
|
19338
19231
|
if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
19339
|
-
const body = bodyArg === "-" ?
|
|
19232
|
+
const body = bodyArg === "-" ? readFileSync21(0, "utf-8") : bodyArg;
|
|
19340
19233
|
const thread = getArg4(rest, "--thread");
|
|
19341
19234
|
const { created, changed } = await saveChannelMemory(
|
|
19342
19235
|
{
|
|
@@ -19420,10 +19313,10 @@ async function runMcpCmd(args) {
|
|
|
19420
19313
|
}
|
|
19421
19314
|
|
|
19422
19315
|
// src/commands/dashboard.ts
|
|
19423
|
-
import { spawn as
|
|
19316
|
+
import { spawn as spawn6, spawnSync } from "child_process";
|
|
19424
19317
|
import { existsSync as existsSync28 } from "fs";
|
|
19425
19318
|
import { platform as platform7 } from "os";
|
|
19426
|
-
import { join as
|
|
19319
|
+
import { join as join26 } from "path";
|
|
19427
19320
|
function flagValue(args, name) {
|
|
19428
19321
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
19429
19322
|
if (eq) return eq.slice(`--${name}=`.length);
|
|
@@ -19433,13 +19326,13 @@ function flagValue(args, name) {
|
|
|
19433
19326
|
function openBrowser2(url) {
|
|
19434
19327
|
const cmd = platform7() === "darwin" ? "open" : platform7() === "win32" ? "start" : "xdg-open";
|
|
19435
19328
|
try {
|
|
19436
|
-
|
|
19329
|
+
spawn6(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
19437
19330
|
} catch {
|
|
19438
19331
|
}
|
|
19439
19332
|
}
|
|
19440
19333
|
function ensureWebBuild() {
|
|
19441
|
-
if (existsSync28(
|
|
19442
|
-
if (!existsSync28(
|
|
19334
|
+
if (existsSync28(join26(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
|
|
19335
|
+
if (!existsSync28(join26(paths.PACKAGE_ROOT, "web", "package.json"))) return;
|
|
19443
19336
|
console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
|
|
19444
19337
|
const result = spawnSync("pnpm", ["--dir", "web", "build"], {
|
|
19445
19338
|
cwd: paths.PACKAGE_ROOT,
|
|
@@ -19802,7 +19695,7 @@ async function main() {
|
|
|
19802
19695
|
break;
|
|
19803
19696
|
case "-v":
|
|
19804
19697
|
case "--version":
|
|
19805
|
-
printRaw("0.1.
|
|
19698
|
+
printRaw("0.1.15");
|
|
19806
19699
|
break;
|
|
19807
19700
|
case "-h":
|
|
19808
19701
|
case "--help":
|