@dv.nghiem/flowdeck 0.3.1 → 0.3.3

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync24 } from "fs";
3
- import { join as join23, basename } from "path";
2
+ import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
3
+ import { join as join24, basename } from "path";
4
4
  import { dirname as dirname4 } from "path";
5
5
  import { fileURLToPath as fileURLToPath2 } from "url";
6
6
 
@@ -1731,6 +1731,76 @@ var memorySearchTool = tool16({
1731
1731
  }
1732
1732
  });
1733
1733
 
1734
+ // src/tools/memory-status.ts
1735
+ import { tool as tool17 } from "@opencode-ai/plugin";
1736
+ import { Database as Database2 } from "bun:sqlite";
1737
+ import { existsSync as existsSync14 } from "fs";
1738
+ import { join as join14 } from "path";
1739
+ import { homedir as homedir2 } from "os";
1740
+ var DB_PATH2 = join14(homedir2(), ".flowdeck-memory", "memory.db");
1741
+ var memoryStatusTool = tool17({
1742
+ description: "Check FlowDeck memory database status, statistics, and recent sessions",
1743
+ args: {},
1744
+ async execute(_args, _context) {
1745
+ try {
1746
+ const exists = existsSync14(DB_PATH2);
1747
+ const result = {
1748
+ database_exists: exists,
1749
+ path: DB_PATH2,
1750
+ status: exists ? "ACTIVE" : "NOT_INITIALIZED",
1751
+ statistics: null
1752
+ };
1753
+ if (exists) {
1754
+ try {
1755
+ const db2 = new Database2(DB_PATH2);
1756
+ const sessions = db2.prepare("SELECT COUNT(*) as count FROM sessions").get();
1757
+ const observations = db2.prepare("SELECT COUNT(*) as count FROM observations").get();
1758
+ const summaries = db2.prepare("SELECT COUNT(*) as count FROM summaries").get();
1759
+ const recentSessions = db2.prepare(`
1760
+ SELECT
1761
+ id,
1762
+ content_session_id,
1763
+ project,
1764
+ directory,
1765
+ created_at,
1766
+ last_active_at,
1767
+ prompt_count
1768
+ FROM sessions
1769
+ ORDER BY last_active_at DESC
1770
+ LIMIT 5
1771
+ `).all();
1772
+ result.statistics = {
1773
+ sessions: sessions.count,
1774
+ observations: observations.count,
1775
+ summaries: summaries.count,
1776
+ recent_sessions: recentSessions.map((s) => {
1777
+ const obsCount = db2.prepare("SELECT COUNT(*) as count FROM observations WHERE session_id = ?").get(s.id);
1778
+ return {
1779
+ project: s.project,
1780
+ directory: s.directory,
1781
+ observations_in_session: obsCount.count,
1782
+ last_active: s.last_active_at,
1783
+ prompt_count: s.prompt_count
1784
+ };
1785
+ })
1786
+ };
1787
+ db2.close();
1788
+ } catch (err) {
1789
+ result.status = "ERROR";
1790
+ result.statistics = { error: String(err) };
1791
+ }
1792
+ }
1793
+ return JSON.stringify(result, null, 2);
1794
+ } catch (err) {
1795
+ return JSON.stringify({
1796
+ status: "ERROR",
1797
+ error: String(err),
1798
+ path: DB_PATH2
1799
+ }, null, 2);
1800
+ }
1801
+ }
1802
+ });
1803
+
1734
1804
  // src/hooks/memory-hook.ts
1735
1805
  var MAX_TOOL_RESPONSE = 1e4;
1736
1806
  var MAX_PROMPT_LENGTH = 2000;
@@ -1830,13 +1900,13 @@ var memoryHook = {
1830
1900
  };
1831
1901
 
1832
1902
  // src/hooks/guard-rails.ts
1833
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
1834
- import { join as join14 } from "path";
1903
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
1904
+ import { join as join15 } from "path";
1835
1905
  var PLANNING_DIR2 = ".planning";
1836
1906
  var CONFIG_FILE = "config.json";
1837
1907
  var STATE_FILE2 = "STATE.md";
1838
1908
  function resolveExecutionMode(configPath, trustScore, volatility) {
1839
- if (existsSync14(configPath)) {
1909
+ if (existsSync15(configPath)) {
1840
1910
  try {
1841
1911
  const config = JSON.parse(readFileSync13(configPath, "utf-8"));
1842
1912
  if (config.execution_mode === "review-only")
@@ -1892,22 +1962,22 @@ async function guardRailsHook(ctx, input, _output) {
1892
1962
  if (!ENABLED)
1893
1963
  return;
1894
1964
  const dir = ctx.directory;
1895
- const planningDirPath = join14(dir, PLANNING_DIR2);
1965
+ const planningDirPath = join15(dir, PLANNING_DIR2);
1896
1966
  const codebaseDirectory = codebaseDir(dir);
1897
- const configPath = join14(planningDirPath, CONFIG_FILE);
1898
- const statePath2 = join14(planningDirPath, STATE_FILE2);
1967
+ const configPath = join15(planningDirPath, CONFIG_FILE);
1968
+ const statePath2 = join15(planningDirPath, STATE_FILE2);
1899
1969
  const workspaceRoot = findWorkspaceRoot(dir);
1900
1970
  if (workspaceRoot && dir !== workspaceRoot) {
1901
1971
  const config = getWorkspaceConfig(dir);
1902
- if (config && config.workspace_mode === "shared" && !existsSync14(planningDirPath)) {
1972
+ if (config && config.workspace_mode === "shared" && !existsSync15(planningDirPath)) {
1903
1973
  const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
1904
1974
  throw new Error(`[flowdeck] BLOCK: ${msg}`);
1905
1975
  }
1906
1976
  }
1907
1977
  if (input.tool === "write" || input.tool === "edit") {
1908
- if (!existsSync14(planningDirPath))
1978
+ if (!existsSync15(planningDirPath))
1909
1979
  return;
1910
- if (!existsSync14(codebaseDirectory)) {
1980
+ if (!existsSync15(codebaseDirectory)) {
1911
1981
  throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /map-codebase to map the codebase.`);
1912
1982
  }
1913
1983
  const execMode = resolveExecutionMode(configPath, null);
@@ -1940,7 +2010,7 @@ async function guardRailsHook(ctx, input, _output) {
1940
2010
  }
1941
2011
  }
1942
2012
  function effectiveSeverity(configPath, statePath2) {
1943
- if (existsSync14(configPath)) {
2013
+ if (existsSync15(configPath)) {
1944
2014
  try {
1945
2015
  const configContent = readFileSync13(configPath, "utf-8");
1946
2016
  const config = JSON.parse(configContent);
@@ -1958,7 +2028,7 @@ function getEffectiveSeverity(configPath, statePath2) {
1958
2028
  return effectiveSeverity(configPath, statePath2);
1959
2029
  }
1960
2030
  function getPlanConfirmed(statePath2) {
1961
- if (!existsSync14(statePath2))
2031
+ if (!existsSync15(statePath2))
1962
2032
  return false;
1963
2033
  try {
1964
2034
  const content = readFileSync13(statePath2, "utf-8");
@@ -1969,32 +2039,32 @@ function getPlanConfirmed(statePath2) {
1969
2039
  }
1970
2040
  }
1971
2041
  function getWarningMessage(planningDir2) {
1972
- if (!existsSync14(join14(planningDir2, STATE_FILE2))) {
2042
+ if (!existsSync15(join15(planningDir2, STATE_FILE2))) {
1973
2043
  return "No .planning/ found. Run /new-project first.";
1974
2044
  }
1975
2045
  return "Plan not confirmed. Run /plan and confirm to enable execution.";
1976
2046
  }
1977
2047
  function getBlockMessage(planningDir2) {
1978
- if (!existsSync14(join14(planningDir2, STATE_FILE2))) {
2048
+ if (!existsSync15(join15(planningDir2, STATE_FILE2))) {
1979
2049
  return "No .planning/ found. Run /new-project first.";
1980
2050
  }
1981
2051
  return "Plan not confirmed. Run /plan and confirm to enable execution.";
1982
2052
  }
1983
2053
 
1984
2054
  // src/hooks/tool-guard.ts
1985
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
1986
- import { join as join15 } from "path";
2055
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
2056
+ import { join as join16 } from "path";
1987
2057
  var IS_ENABLED = () => process.env.FLOWDECK_TOOL_GUARD_ENABLED === "on";
1988
2058
  var BLOCKED_PATTERNS = {
1989
2059
  read: [".env", ".pem", ".key", ".secret"],
1990
2060
  write: ["node_modules"],
1991
2061
  bash: ["rm -rf"]
1992
2062
  };
1993
- function isBlocked(tool17, args) {
1994
- const patterns = BLOCKED_PATTERNS[tool17];
2063
+ function isBlocked(tool18, args) {
2064
+ const patterns = BLOCKED_PATTERNS[tool18];
1995
2065
  if (!patterns)
1996
2066
  return null;
1997
- if (tool17 === "bash") {
2067
+ if (tool18 === "bash") {
1998
2068
  const cmd = args.command;
1999
2069
  if (!cmd)
2000
2070
  return null;
@@ -2005,7 +2075,7 @@ function isBlocked(tool17, args) {
2005
2075
  }
2006
2076
  return null;
2007
2077
  }
2008
- if (tool17 === "read") {
2078
+ if (tool18 === "read") {
2009
2079
  const filePath = args.filePath;
2010
2080
  if (!filePath)
2011
2081
  return null;
@@ -2016,7 +2086,7 @@ function isBlocked(tool17, args) {
2016
2086
  }
2017
2087
  return null;
2018
2088
  }
2019
- if (tool17 === "write") {
2089
+ if (tool18 === "write") {
2020
2090
  const filePath = args.filePath;
2021
2091
  if (!filePath)
2022
2092
  return null;
@@ -2030,8 +2100,8 @@ function isBlocked(tool17, args) {
2030
2100
  return null;
2031
2101
  }
2032
2102
  function checkArchConstraint(directory, filePath) {
2033
- const constraintsPath = join15(codebaseDir(directory), "CONSTRAINTS.md");
2034
- if (!existsSync15(constraintsPath))
2103
+ const constraintsPath = join16(codebaseDir(directory), "CONSTRAINTS.md");
2104
+ if (!existsSync16(constraintsPath))
2035
2105
  return null;
2036
2106
  try {
2037
2107
  const content = readFileSync14(constraintsPath, "utf-8");
@@ -2081,18 +2151,18 @@ async function toolGuardHook(ctx, input, output) {
2081
2151
  }
2082
2152
 
2083
2153
  // src/hooks/session-start.ts
2084
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
2154
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
2085
2155
  async function sessionStartHook(ctx) {
2086
2156
  const planningDir2 = ctx.directory + "/.planning";
2087
2157
  const codebaseDirectory = codebaseDir(ctx.directory);
2088
2158
  const workspaceRoot = findWorkspaceRoot(ctx.directory);
2089
2159
  const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
2090
- if (!existsSync16(planningDir2)) {
2160
+ if (!existsSync17(planningDir2)) {
2091
2161
  return {
2092
2162
  flowdeck_phase: null,
2093
2163
  flowdeck_status: "no_plan",
2094
2164
  flowdeck_warning: "Run /new-project or /map-codebase to initialize.",
2095
- flowdeck_has_codebase: existsSync16(codebaseDirectory),
2165
+ flowdeck_has_codebase: existsSync17(codebaseDirectory),
2096
2166
  ...workspaceRoot && config?.sub_repos ? {
2097
2167
  flowdeck_workspace_root: workspaceRoot,
2098
2168
  flowdeck_sub_repos: config.sub_repos,
@@ -2111,7 +2181,7 @@ async function sessionStartHook(ctx) {
2111
2181
  flowdeck_status: currentPhase["status"] ?? null,
2112
2182
  flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
2113
2183
  flowdeck_last_action: currentPhase["last_action"] ?? null,
2114
- flowdeck_has_codebase: existsSync16(codebaseDirectory)
2184
+ flowdeck_has_codebase: existsSync17(codebaseDirectory)
2115
2185
  };
2116
2186
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
2117
2187
  result.flowdeck_workspace_root = workspaceRoot;
@@ -2126,7 +2196,7 @@ async function sessionStartHook(ctx) {
2126
2196
  flowdeck_phase: null,
2127
2197
  flowdeck_status: "error",
2128
2198
  flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
2129
- flowdeck_has_codebase: existsSync16(codebaseDirectory)
2199
+ flowdeck_has_codebase: existsSync17(codebaseDirectory)
2130
2200
  };
2131
2201
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
2132
2202
  result.flowdeck_workspace_root = workspaceRoot;
@@ -2187,13 +2257,13 @@ function tryTerminalBell() {
2187
2257
  function notifySessionIdle() {
2188
2258
  notify("FlowDeck Task Completed", "Agent is idle and waiting for your next instruction", "info");
2189
2259
  }
2190
- function notifyPermissionNeeded(tool17) {
2191
- notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool17}`, "critical");
2260
+ function notifyPermissionNeeded(tool18) {
2261
+ notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool18}`, "critical");
2192
2262
  }
2193
2263
 
2194
2264
  // src/hooks/patch-trust.ts
2195
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
2196
- import { join as join16 } from "path";
2265
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
2266
+ import { join as join17 } from "path";
2197
2267
  var HIGH_RISK_KEYWORDS = [
2198
2268
  "password",
2199
2269
  "secret",
@@ -2215,8 +2285,8 @@ var HIGH_RISK_KEYWORDS = [
2215
2285
  "privilege"
2216
2286
  ];
2217
2287
  function loadVolatility(directory) {
2218
- const p = join16(codebaseDir(directory), "VOLATILITY.json");
2219
- if (!existsSync17(p))
2288
+ const p = join17(codebaseDir(directory), "VOLATILITY.json");
2289
+ if (!existsSync18(p))
2220
2290
  return {};
2221
2291
  try {
2222
2292
  const data = JSON.parse(readFileSync16(p, "utf-8"));
@@ -2229,8 +2299,8 @@ function loadVolatility(directory) {
2229
2299
  }
2230
2300
  }
2231
2301
  function loadFailedPaths(directory) {
2232
- const p = join16(codebaseDir(directory), "FAILURES.json");
2233
- if (!existsSync17(p))
2302
+ const p = join17(codebaseDir(directory), "FAILURES.json");
2303
+ if (!existsSync18(p))
2234
2304
  return [];
2235
2305
  try {
2236
2306
  const data = JSON.parse(readFileSync16(p, "utf-8"));
@@ -2288,14 +2358,18 @@ async function patchTrustHook(ctx, input, output) {
2288
2358
  Signals: ${trust.signals.join("; ")}
2289
2359
  This edit requires explicit human review before applying.`);
2290
2360
  } else if (trust.verdict === "review-required") {
2361
+ const reviewEnabled = process.env.FLOWDECK_PATCH_TRUST_REVIEW_ENABLED;
2362
+ if (reviewEnabled !== "true" && reviewEnabled !== "1") {
2363
+ return;
2364
+ }
2291
2365
  throw new Error(`[flowdeck] PATCH-TRUST REVIEW-REQUIRED (score=${trust.score}): ${filePath}
2292
2366
  Signals: ${trust.signals.join("; ")}`);
2293
2367
  }
2294
2368
  }
2295
2369
 
2296
2370
  // src/hooks/decision-trace-hook.ts
2297
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2 } from "fs";
2298
- import { join as join17 } from "path";
2371
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2 } from "fs";
2372
+ import { join as join18 } from "path";
2299
2373
  async function decisionTraceHook(ctx, input, output) {
2300
2374
  if (input.tool !== "write" && input.tool !== "edit")
2301
2375
  return;
@@ -2304,7 +2378,7 @@ async function decisionTraceHook(ctx, input, output) {
2304
2378
  return;
2305
2379
  const base = codebaseDir(ctx.directory);
2306
2380
  try {
2307
- if (!existsSync18(base))
2381
+ if (!existsSync19(base))
2308
2382
  mkdirSync9(base, { recursive: true });
2309
2383
  const entry = {
2310
2384
  timestamp: new Date().toISOString(),
@@ -2317,23 +2391,23 @@ async function decisionTraceHook(ctx, input, output) {
2317
2391
  risk_level: "unknown",
2318
2392
  auto_recorded: true
2319
2393
  };
2320
- appendFileSync2(join17(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
2394
+ appendFileSync2(join18(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
2321
2395
  `, "utf-8");
2322
2396
  } catch {}
2323
2397
  }
2324
2398
 
2325
2399
  // src/services/telemetry.ts
2326
- import { existsSync as existsSync19, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync10 } from "fs";
2327
- import { join as join18 } from "path";
2400
+ import { existsSync as existsSync20, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync10 } from "fs";
2401
+ import { join as join19 } from "path";
2328
2402
  import { randomUUID } from "crypto";
2329
2403
  function telemetryPath(dir) {
2330
- return join18(codebaseDir(dir), "TELEMETRY.jsonl");
2404
+ return join19(codebaseDir(dir), "TELEMETRY.jsonl");
2331
2405
  }
2332
2406
  function appendEvent(dir, partial) {
2333
2407
  if (process.env.TELEMETRY_ENABLED !== "true")
2334
2408
  return null;
2335
2409
  const cd = codebaseDir(dir);
2336
- if (!existsSync19(cd))
2410
+ if (!existsSync20(cd))
2337
2411
  mkdirSync10(cd, { recursive: true });
2338
2412
  const event = {
2339
2413
  id: randomUUID(),
@@ -2348,31 +2422,31 @@ function appendEvent(dir, partial) {
2348
2422
  // src/hooks/telemetry-hook.ts
2349
2423
  async function telemetryHook(context, toolInput, output) {
2350
2424
  const dir = context.directory ?? process.cwd();
2351
- const tool17 = toolInput.name ?? toolInput.tool ?? "unknown";
2425
+ const tool18 = toolInput.name ?? toolInput.tool ?? "unknown";
2352
2426
  appendEvent(dir, {
2353
2427
  session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
2354
2428
  run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
2355
2429
  event: "tool.call",
2356
- tool: tool17,
2430
+ tool: tool18,
2357
2431
  status: "ok",
2358
2432
  meta: { parameters: output.args ?? {} }
2359
2433
  });
2360
2434
  }
2361
2435
  async function telemetryAfterHook(context, toolInput, _output) {
2362
2436
  const dir = context.directory ?? process.cwd();
2363
- const tool17 = toolInput.name ?? toolInput.tool ?? "unknown";
2437
+ const tool18 = toolInput.name ?? toolInput.tool ?? "unknown";
2364
2438
  appendEvent(dir, {
2365
2439
  session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
2366
2440
  run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
2367
2441
  event: "tool.complete",
2368
- tool: tool17,
2442
+ tool: tool18,
2369
2443
  status: "ok"
2370
2444
  });
2371
2445
  }
2372
2446
 
2373
2447
  // src/services/approval-manager.ts
2374
- import { existsSync as existsSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync11 } from "fs";
2375
- import { join as join19 } from "path";
2448
+ import { existsSync as existsSync21, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync11 } from "fs";
2449
+ import { join as join20 } from "path";
2376
2450
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
2377
2451
  var SENSITIVE_PATTERNS = [
2378
2452
  /auth/i,
@@ -2409,11 +2483,11 @@ function isSensitivePath(filePath) {
2409
2483
  return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
2410
2484
  }
2411
2485
  function approvalsPath(dir) {
2412
- return join19(codebaseDir(dir), "APPROVALS.json");
2486
+ return join20(codebaseDir(dir), "APPROVALS.json");
2413
2487
  }
2414
2488
  function loadStore(dir) {
2415
2489
  const p = approvalsPath(dir);
2416
- if (!existsSync20(p))
2490
+ if (!existsSync21(p))
2417
2491
  return { requests: [] };
2418
2492
  try {
2419
2493
  return JSON.parse(readFileSync18(p, "utf-8"));
@@ -2434,8 +2508,8 @@ async function approvalHook(context, toolInput, output) {
2434
2508
  if (!ENABLED2)
2435
2509
  return;
2436
2510
  const dir = context.directory ?? process.cwd();
2437
- const tool17 = toolInput.name ?? toolInput.tool ?? "";
2438
- if (!WRITE_TOOLS.has(tool17))
2511
+ const tool18 = toolInput.name ?? toolInput.tool ?? "";
2512
+ if (!WRITE_TOOLS.has(tool18))
2439
2513
  return;
2440
2514
  const args = output.args ?? {};
2441
2515
  const filePath = String(args.path ?? args.file_path ?? args.filename ?? "");
@@ -2450,7 +2524,7 @@ async function approvalHook(context, toolInput, output) {
2450
2524
  session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
2451
2525
  run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
2452
2526
  event: "approval.request",
2453
- tool: tool17,
2527
+ tool: tool18,
2454
2528
  status: "blocked",
2455
2529
  files: [filePath],
2456
2530
  meta: { trigger: "sensitive_file", file: filePath }
@@ -2511,8 +2585,8 @@ function createContextWindowMonitorHook() {
2511
2585
  }
2512
2586
 
2513
2587
  // src/hooks/shell-env-hook.ts
2514
- import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
2515
- import { join as join20 } from "path";
2588
+ import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
2589
+ import { join as join21 } from "path";
2516
2590
  import { createRequire } from "module";
2517
2591
  var _version;
2518
2592
  function getVersion() {
@@ -2548,7 +2622,7 @@ var MARKER_TO_LANG = {
2548
2622
  };
2549
2623
  function detectPackageManager(root) {
2550
2624
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
2551
- if (existsSync21(join20(root, lockfile)))
2625
+ if (existsSync22(join21(root, lockfile)))
2552
2626
  return pm;
2553
2627
  }
2554
2628
  return;
@@ -2557,7 +2631,7 @@ function detectLanguages(root) {
2557
2631
  const langs = [];
2558
2632
  const seen = new Set;
2559
2633
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
2560
- if (!seen.has(lang) && existsSync21(join20(root, marker))) {
2634
+ if (!seen.has(lang) && existsSync22(join21(root, marker))) {
2561
2635
  langs.push(lang);
2562
2636
  seen.add(lang);
2563
2637
  }
@@ -2565,8 +2639,8 @@ function detectLanguages(root) {
2565
2639
  return langs;
2566
2640
  }
2567
2641
  function readCurrentPhase(root) {
2568
- const statePath2 = join20(root, ".planning", "STATE.md");
2569
- if (!existsSync21(statePath2))
2642
+ const statePath2 = join21(root, ".planning", "STATE.md");
2643
+ if (!existsSync22(statePath2))
2570
2644
  return;
2571
2645
  try {
2572
2646
  const content = readFileSync19(statePath2, "utf-8");
@@ -2669,8 +2743,8 @@ function createSessionIdleHook(client, tracker) {
2669
2743
  }
2670
2744
 
2671
2745
  // src/hooks/compaction-hook.ts
2672
- import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
2673
- import { join as join21 } from "path";
2746
+ import { existsSync as existsSync23, readFileSync as readFileSync20 } from "fs";
2747
+ import { join as join22 } from "path";
2674
2748
  var STRUCTURED_SUMMARY_PROMPT = `
2675
2749
  When summarizing this session, you MUST include the following sections:
2676
2750
 
@@ -2709,8 +2783,8 @@ For each: agent name, status, description, session_id.
2709
2783
  **RESUME, DON'T RESTART.** Use session_id to continue existing sessions.
2710
2784
  `;
2711
2785
  function readPlanningState2(directory) {
2712
- const statePath2 = join21(directory, ".planning", "STATE.md");
2713
- if (!existsSync22(statePath2))
2786
+ const statePath2 = join22(directory, ".planning", "STATE.md");
2787
+ if (!existsSync23(statePath2))
2714
2788
  return null;
2715
2789
  try {
2716
2790
  const content = readFileSync20(statePath2, "utf-8");
@@ -5806,21 +5880,21 @@ function getAgentConfigs(agentModels) {
5806
5880
  }
5807
5881
 
5808
5882
  // src/config/loader.ts
5809
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5810
- import { join as join22 } from "path";
5811
- import { homedir as homedir2 } from "os";
5883
+ import { existsSync as existsSync24, readFileSync as readFileSync21 } from "fs";
5884
+ import { join as join23 } from "path";
5885
+ import { homedir as homedir3 } from "os";
5812
5886
  var CONFIG_FILENAME = "flowdeck.json";
5813
5887
  function getGlobalConfigDir() {
5814
- return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join22(process.env.XDG_CONFIG_HOME, "opencode") : join22(homedir2(), ".config", "opencode"));
5888
+ return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join23(process.env.XDG_CONFIG_HOME, "opencode") : join23(homedir3(), ".config", "opencode"));
5815
5889
  }
5816
5890
  function loadFlowDeckConfig(directory) {
5817
5891
  const candidates = [];
5818
5892
  if (directory) {
5819
- candidates.push(join22(directory, ".opencode", CONFIG_FILENAME));
5893
+ candidates.push(join23(directory, ".opencode", CONFIG_FILENAME));
5820
5894
  }
5821
- candidates.push(join22(getGlobalConfigDir(), CONFIG_FILENAME));
5895
+ candidates.push(join23(getGlobalConfigDir(), CONFIG_FILENAME));
5822
5896
  for (const configPath of candidates) {
5823
- if (existsSync23(configPath)) {
5897
+ if (existsSync24(configPath)) {
5824
5898
  try {
5825
5899
  const content = readFileSync21(configPath, "utf-8");
5826
5900
  return JSON.parse(content);
@@ -5834,13 +5908,13 @@ function loadFlowDeckConfig(directory) {
5834
5908
  // src/index.ts
5835
5909
  function loadRulePaths() {
5836
5910
  const __dir = dirname4(fileURLToPath2(import.meta.url));
5837
- const rulesDir = join23(__dir, "..", "src", "rules");
5838
- if (!existsSync24(rulesDir))
5911
+ const rulesDir = join24(__dir, "..", "src", "rules");
5912
+ if (!existsSync25(rulesDir))
5839
5913
  return [];
5840
5914
  const paths = [];
5841
5915
  function walk(dir) {
5842
5916
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
5843
- const full = join23(dir, entry.name);
5917
+ const full = join24(dir, entry.name);
5844
5918
  if (entry.isDirectory()) {
5845
5919
  walk(full);
5846
5920
  } else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
@@ -5853,8 +5927,8 @@ function loadRulePaths() {
5853
5927
  }
5854
5928
  function loadCommands() {
5855
5929
  const __dir = dirname4(fileURLToPath2(import.meta.url));
5856
- const commandsDir = join23(__dir, "..", "src", "commands");
5857
- if (!existsSync24(commandsDir))
5930
+ const commandsDir = join24(__dir, "..", "src", "commands");
5931
+ if (!existsSync25(commandsDir))
5858
5932
  return {};
5859
5933
  const commands = {};
5860
5934
  try {
@@ -5862,7 +5936,7 @@ function loadCommands() {
5862
5936
  if (!file.endsWith(".md"))
5863
5937
  continue;
5864
5938
  const name = basename(file, ".md");
5865
- const raw = readFileSync22(join23(commandsDir, file), "utf-8");
5939
+ const raw = readFileSync22(join24(commandsDir, file), "utf-8");
5866
5940
  let description;
5867
5941
  let template = raw;
5868
5942
  const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
@@ -5939,8 +6013,8 @@ var plugin = async (input, _options) => {
5939
6013
  }
5940
6014
  }
5941
6015
  }
5942
- const skillsDir = join23(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
5943
- if (existsSync24(skillsDir)) {
6016
+ const skillsDir = join24(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
6017
+ if (existsSync25(skillsDir)) {
5944
6018
  const cfgAny = cfg;
5945
6019
  if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
5946
6020
  cfgAny.skills = { paths: [] };
@@ -5981,7 +6055,8 @@ var plugin = async (input, _options) => {
5981
6055
  "context-generator": contextGeneratorTool,
5982
6056
  "create-skill": createSkillTool,
5983
6057
  reflect: reflectTool,
5984
- "memory-search": memorySearchTool
6058
+ "memory-search": memorySearchTool,
6059
+ "memory-status": memoryStatusTool
5985
6060
  },
5986
6061
  "shell.env": shellEnvHook,
5987
6062
  "todo.updated": todoHook,
@@ -5993,30 +6068,34 @@ var plugin = async (input, _options) => {
5993
6068
  },
5994
6069
  event: async ({ event }) => {
5995
6070
  const type = event?.type ?? "";
5996
- if (type === "session.created" || type === "session.started") {
5997
- const sessionId = event?.sessionID ?? event?.sessionId ?? "";
5998
- if (sessionId) {
5999
- memoryHook.onSessionCreated(directory, sessionId, event?.prompt);
6000
- }
6001
- await sessionStartHook({ directory });
6002
- } else if (type === "message.updated" && event?.event) {
6003
- const msgEvent = event.event;
6004
- const sessionId = msgEvent?.sessionID ?? msgEvent?.sessionId ?? "";
6005
- if (sessionId) {
6006
- memoryHook.onMessageUpdated(sessionId, msgEvent.role, msgEvent.content, directory);
6007
- }
6008
- } else if (type === "session.compacted" && event?.event) {
6009
- const compactEvent = event.event;
6010
- const sessionId = compactEvent?.sessionID ?? compactEvent?.sessionId ?? "";
6011
- if (sessionId) {
6012
- memoryHook.onSessionCompact(sessionId, compactEvent.summary ?? "");
6013
- }
6014
- } else if (type === "session.deleted" && event?.event) {
6015
- const delEvent = event.event;
6016
- const sessionId = delEvent?.sessionID ?? delEvent?.sessionId ?? "";
6017
- if (sessionId) {
6018
- memoryHook.clearSession(sessionId);
6071
+ try {
6072
+ if (type === "session.created" || type === "session.started") {
6073
+ const sessionId = event?.sessionID ?? event?.sessionId ?? "";
6074
+ if (sessionId) {
6075
+ memoryHook.onSessionCreated(directory, sessionId, event?.prompt);
6076
+ }
6077
+ await sessionStartHook({ directory });
6078
+ } else if (type === "message.updated") {
6079
+ const msgEvent = event?.event ?? event;
6080
+ const sessionId = msgEvent?.sessionID ?? msgEvent?.sessionId ?? "";
6081
+ if (sessionId) {
6082
+ memoryHook.onMessageUpdated(sessionId, msgEvent.role, msgEvent.content, directory);
6083
+ }
6084
+ } else if (type === "session.compacted") {
6085
+ const compactEvent = event?.event ?? event;
6086
+ const sessionId = compactEvent?.sessionID ?? compactEvent?.sessionId ?? "";
6087
+ if (sessionId) {
6088
+ memoryHook.onSessionCompact(sessionId, compactEvent.summary ?? "");
6089
+ }
6090
+ } else if (type === "session.deleted") {
6091
+ const delEvent = event?.event ?? event;
6092
+ const sessionId = delEvent?.sessionID ?? delEvent?.sessionId ?? "";
6093
+ if (sessionId) {
6094
+ memoryHook.clearSession(sessionId);
6095
+ }
6019
6096
  }
6097
+ } catch (err) {
6098
+ console.error("[FlowDeck Memory] Event handler error:", err);
6020
6099
  }
6021
6100
  await contextMonitor.event({ event });
6022
6101
  orchestratorGuard.onEvent(event);
@@ -6026,6 +6105,16 @@ var plugin = async (input, _options) => {
6026
6105
  }
6027
6106
  },
6028
6107
  "tool.execute.before": async (toolInput, toolOutput) => {
6108
+ if ((toolInput.tool === "read" || toolInput.tool === "view") && toolOutput?.args) {
6109
+ if (typeof toolOutput.args.offset === "string") {
6110
+ const n = Number(toolOutput.args.offset);
6111
+ if (!isNaN(n))
6112
+ toolOutput.args.offset = Math.floor(n);
6113
+ }
6114
+ if (Array.isArray(toolOutput.args.view_range)) {
6115
+ toolOutput.args.view_range = toolOutput.args.view_range.map((v) => typeof v === "string" ? Math.floor(Number(v)) : v);
6116
+ }
6117
+ }
6029
6118
  orchestratorGuard.check(toolInput.sessionID ?? "", toolInput.tool ?? toolInput.name ?? "");
6030
6119
  await telemetryHook({ directory }, toolInput, toolOutput);
6031
6120
  await approvalHook({ directory }, toolInput, toolOutput);
@@ -6036,9 +6125,13 @@ var plugin = async (input, _options) => {
6036
6125
  },
6037
6126
  "tool.execute.after": async (toolInput, toolOutput) => {
6038
6127
  await telemetryAfterHook({ directory }, toolInput, toolOutput);
6039
- const sessionId = toolInput?.sessionID ?? toolInput?.sessionId ?? "";
6040
- if (sessionId && toolInput?.tool) {
6041
- memoryHook.onToolExecuted(sessionId, toolInput.tool, toolInput, toolOutput?.output ?? null, directory);
6128
+ try {
6129
+ const sessionId = toolInput?.sessionID ?? toolInput?.sessionId ?? "";
6130
+ if (sessionId && toolInput?.tool) {
6131
+ memoryHook.onToolExecuted(sessionId, toolInput.tool, toolInput, toolOutput?.output ?? null, directory);
6132
+ }
6133
+ } catch (err) {
6134
+ console.error("[FlowDeck Memory] Tool execution error:", err);
6042
6135
  }
6043
6136
  await contextMonitor["tool.execute.after"](toolInput, toolOutput);
6044
6137
  }
@@ -0,0 +1,3 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin";
2
+ export declare const memoryStatusTool: ToolDefinition;
3
+ //# sourceMappingURL=memory-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-status.d.ts","sourceRoot":"","sources":["../../src/tools/memory-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAQ/D,eAAO,MAAM,gBAAgB,EAAE,cAoE7B,CAAA"}