@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/README.md +11 -7
- package/dist/hooks/patch-trust.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -108
- package/dist/tools/memory-status.d.ts +3 -0
- package/dist/tools/memory-status.d.ts.map +1 -0
- package/docs/commands.md +102 -9
- package/docs/quick-start.md +44 -23
- package/docs/workflows.md +10 -8
- package/package.json +1 -1
- package/src/commands/fd-execute.md +192 -0
- package/src/commands/fd-new-feature.md +44 -157
- package/src/commands/fd-new-project.md +1 -2
- package/src/commands/fd-plan.md +1 -1
- package/src/commands/fd-suggest.md +84 -0
- package/src/commands/fd-verify.md +126 -0
- package/src/rules/common/agent-orchestration.md +5 -5
- package/src/rules/common/coding-style.md +17 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as
|
|
3
|
-
import { join as
|
|
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
|
|
1834
|
-
import { join as
|
|
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 (
|
|
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 =
|
|
1965
|
+
const planningDirPath = join15(dir, PLANNING_DIR2);
|
|
1896
1966
|
const codebaseDirectory = codebaseDir(dir);
|
|
1897
|
-
const configPath =
|
|
1898
|
-
const statePath2 =
|
|
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" && !
|
|
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 (!
|
|
1978
|
+
if (!existsSync15(planningDirPath))
|
|
1909
1979
|
return;
|
|
1910
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
1986
|
-
import { join as
|
|
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(
|
|
1994
|
-
const patterns = BLOCKED_PATTERNS[
|
|
2063
|
+
function isBlocked(tool18, args) {
|
|
2064
|
+
const patterns = BLOCKED_PATTERNS[tool18];
|
|
1995
2065
|
if (!patterns)
|
|
1996
2066
|
return null;
|
|
1997
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
2034
|
-
if (!
|
|
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
|
|
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 (!
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
2191
|
-
notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${
|
|
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
|
|
2196
|
-
import { join as
|
|
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 =
|
|
2219
|
-
if (!
|
|
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 =
|
|
2233
|
-
if (!
|
|
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
|
|
2298
|
-
import { join as
|
|
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 (!
|
|
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(
|
|
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
|
|
2327
|
-
import { join as
|
|
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
|
|
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 (!
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
2442
|
+
tool: tool18,
|
|
2369
2443
|
status: "ok"
|
|
2370
2444
|
});
|
|
2371
2445
|
}
|
|
2372
2446
|
|
|
2373
2447
|
// src/services/approval-manager.ts
|
|
2374
|
-
import { existsSync as
|
|
2375
|
-
import { join as
|
|
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
|
|
2486
|
+
return join20(codebaseDir(dir), "APPROVALS.json");
|
|
2413
2487
|
}
|
|
2414
2488
|
function loadStore(dir) {
|
|
2415
2489
|
const p = approvalsPath(dir);
|
|
2416
|
-
if (!
|
|
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
|
|
2438
|
-
if (!WRITE_TOOLS.has(
|
|
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:
|
|
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
|
|
2515
|
-
import { join as
|
|
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 (
|
|
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) &&
|
|
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 =
|
|
2569
|
-
if (!
|
|
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
|
|
2673
|
-
import { join as
|
|
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 =
|
|
2713
|
-
if (!
|
|
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
|
|
5810
|
-
import { join as
|
|
5811
|
-
import { homedir as
|
|
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 ?
|
|
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(
|
|
5893
|
+
candidates.push(join23(directory, ".opencode", CONFIG_FILENAME));
|
|
5820
5894
|
}
|
|
5821
|
-
candidates.push(
|
|
5895
|
+
candidates.push(join23(getGlobalConfigDir(), CONFIG_FILENAME));
|
|
5822
5896
|
for (const configPath of candidates) {
|
|
5823
|
-
if (
|
|
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 =
|
|
5838
|
-
if (!
|
|
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 =
|
|
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 =
|
|
5857
|
-
if (!
|
|
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(
|
|
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 =
|
|
5943
|
-
if (
|
|
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
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
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
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
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 @@
|
|
|
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"}
|