@shawnstack/quickforge 1.6.10 → 1.6.11
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 +10 -10
- package/dist/assets/{AgentProfilesPage-BtZpDV-3.js → AgentProfilesPage-CvoB0OId.js} +1 -1
- package/dist/assets/{ChatPanelHost-BF1V-xOV.js → ChatPanelHost-Cyi1yi_P.js} +57 -57
- package/dist/assets/{PluginsPage-CCIj8-Mn.js → PluginsPage-B_4nlPca.js} +1 -1
- package/dist/assets/{ScheduledTasksPage-DIMewQ3i.js → ScheduledTasksPage-Zbsuo8o6.js} +1 -1
- package/dist/assets/{SettingsWorkspacePage-BPD495WT.js → SettingsWorkspacePage-BtgkwIKN.js} +190 -120
- package/dist/assets/{SharedConversationPage-B_oMTp7B.js → SharedConversationPage-B5FS3eSl.js} +1 -1
- package/dist/assets/{TerminalDock-wgW7kSC4.js → TerminalDock-CiJCmyqg.js} +1 -1
- package/dist/assets/{WorkspaceInspector-CdX45r3u.js → WorkspaceInspector-C9HyQIvF.js} +1 -1
- package/dist/assets/index-BFKildY3.js +63 -0
- package/dist/assets/{index-Bxg5fT2p.css → index-CsKSKUn-.css} +1 -1
- package/dist/assets/{mcp-servers-dialog-jHlDR9jP.js → mcp-servers-dialog-BPk4a4nW.js} +1 -1
- package/dist/assets/{skills-dialog-DvSnPPzu.js → skills-dialog-DcxeFWSt.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/server/agent-manager.mjs +45 -3
- package/server/approval-store.mjs +4 -0
- package/server/global-memory.mjs +168 -0
- package/server/index.mjs +7 -0
- package/server/project-config.mjs +3 -0
- package/server/routes/memory.mjs +26 -0
- package/server/routes/tools.mjs +5 -3
- package/server/system-prompt.mjs +22 -0
- package/server/tools/definitions.mjs +14 -0
- package/server/tools/index.mjs +6 -0
- package/dist/assets/index-DKmdePUP.js +0 -63
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{B as t,C as n,D as r,Rt as i,Vt as a,a as o,g as s,st as c,x as l,y as u}from"./icons-C7j5jdKo.js";import{n as d}from"./react-vendor-VqdnQHHS.js";import{H as f,ct as p,dt as m,ht as h,ut as g,x as _}from"./index-
|
|
1
|
+
import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{B as t,C as n,D as r,Rt as i,Vt as a,a as o,g as s,st as c,x as l,y as u}from"./icons-C7j5jdKo.js";import{n as d}from"./react-vendor-VqdnQHHS.js";import{H as f,ct as p,dt as m,ht as h,ut as g,x as _}from"./index-BFKildY3.js";var v=e(a(),1),y=d();function b(e){return e===`connected`?`bg-emerald-500/12 text-emerald-700 dark:text-emerald-300`:e===`error`?`bg-destructive/12 text-destructive`:e===`disabled`?`bg-muted text-muted-foreground`:`bg-amber-500/12 text-amber-700 dark:text-amber-300`}var x=12;function S({server:e,toggling:n,reconnecting:i,onToggle:a,onEdit:s,onDelete:c,onReconnect:l}){let d=e.tools?.slice(0,x)??[],f=e.toolCount??e.tools?.length??0,p=Math.max(0,f-d.length),_=e.enabled&&e.status!==`connected`;return(0,y.jsxs)(`div`,{className:`rounded-lg border border-border p-3`,children:[(0,y.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,y.jsxs)(`div`,{className:`min-w-0`,children:[(0,y.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,y.jsx)(`div`,{className:`truncate text-sm font-medium text-foreground/90`,children:e.name}),(0,y.jsx)(`span`,{className:m(`rounded-full px-2 py-0.5 text-[11px]`,b(e.status)),children:e.status||`unknown`}),(0,y.jsx)(`span`,{className:`text-[11px] text-muted-foreground/60`,children:h(`mcpToolsCount`,{count:f})})]}),(0,y.jsx)(`div`,{className:`mt-1 truncate text-xs text-muted-foreground/65`,children:e.transport===`stdio`?`${e.command} ${(e.args||[]).join(` `)}`:e.url}),e.error?(0,y.jsx)(`div`,{className:`mt-1 text-xs text-destructive`,children:e.error}):null]}),(0,y.jsxs)(`div`,{className:`flex shrink-0 items-center gap-1`,children:[(0,y.jsx)(`button`,{type:`button`,role:`switch`,"aria-checked":e.enabled,disabled:n,className:m(`relative h-6 w-11 rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60`,e.enabled?`bg-emerald-500`:`bg-muted-foreground/30`),onClick:()=>a(e),title:e.enabled?h(`pauseTask`):h(`enable`),children:(0,y.jsx)(`span`,{className:m(`absolute left-0.5 top-0.5 size-5 rounded-full bg-white shadow transition-transform`,e.enabled?`translate-x-5`:`translate-x-0`)})}),_?(0,y.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>l(e.name),disabled:i,"aria-label":h(`mcpReconnectServer`),title:h(`mcpReconnectServer`),children:i?(0,y.jsx)(t,{className:`size-4 animate-spin`}):(0,y.jsx)(u,{className:`size-4`})}):null,(0,y.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>s(e),"aria-label":h(`editTask`),title:h(`editTask`),children:(0,y.jsx)(r,{className:`size-4`})}),(0,y.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-destructive`,onClick:()=>c(e.name),"aria-label":h(`delete`),title:h(`delete`),children:(0,y.jsx)(o,{className:`size-4`})})]})]}),d.length>0?(0,y.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[d.map(e=>(0,y.jsx)(`span`,{className:`rounded-md bg-muted/28 px-1.5 py-0.5 text-[11px] text-muted-foreground/75`,title:e.quickForgeName,children:e.name},e.quickForgeName)),p>0?(0,y.jsx)(`span`,{className:`rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground/55`,children:h(`mcpMoreTools`,{count:p})}):null]}):null]})}function C(){return{name:``,transport:`stdio`,command:``,args:[],url:``,cwd:``,env:{}}}function w(e){return{name:e.name,transport:e.transport,command:e.command,args:e.args||[],url:e.url||``,cwd:e.cwd||``,env:e.env||{}}}function T(e){return(e||[]).join(`
|
|
2
2
|
`)}function E(e){return Object.entries(e||{}).map(([e,t])=>`${e}=${t}`).join(`
|
|
3
3
|
`)}function D(e){return e.split(`
|
|
4
4
|
`).map(e=>e.trim()).filter(Boolean)}function O(e){let t={};for(let n of e.split(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e,n as t}from"./rolldown-runtime-DWdDZTNf.js";import{B as n,It as r,Rt as i,Vt as a,_ as o,n as s}from"./icons-C7j5jdKo.js";import{n as c}from"./react-vendor-VqdnQHHS.js";import{ct as l,dt as u,ht as d}from"./index-
|
|
1
|
+
import{i as e,n as t}from"./rolldown-runtime-DWdDZTNf.js";import{B as n,It as r,Rt as i,Vt as a,_ as o,n as s}from"./icons-C7j5jdKo.js";import{n as c}from"./react-vendor-VqdnQHHS.js";import{ct as l,dt as u,ht as d}from"./index-BFKildY3.js";var f=t({SkillsDialog:()=>_,SkillsManagerPanel:()=>g}),p=e(a(),1),m=c();async function h(e){let t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||`HTTP ${e.status}`);return t}function g({active:e=!0,scope:t,project:a,embedded:c=!1,onClose:f,onSaved:g}){let[_,v]=(0,p.useState)([]),[y,b]=(0,p.useState)(()=>new Set),[x,S]=(0,p.useState)(``),[C,w]=(0,p.useState)([]),[T,E]=(0,p.useState)(!1),[D,O]=(0,p.useState)(!1),[k,A]=(0,p.useState)(``),j=t===`project`,[M,N]=(0,p.useState)(null),[P,F]=(0,p.useState)(null),[I,L]=(0,p.useState)(!1),[R,z]=(0,p.useState)(``),B=(0,p.useCallback)(()=>{N(null),F(null),z(``)},[]),V=(0,p.useCallback)(()=>{B(),f?.()},[f,B]);(0,p.useEffect)(()=>{if(!e||j&&!a)return;let t=!1;return(async()=>{E(!0),A(``);try{let e=j?`/api/skills?projectId=${encodeURIComponent(a.id)}`:`/api/skills?scope=global`,n=await h(await fetch(e));if(t)return;v(Array.isArray(n.skills)?n.skills:[]),b(new Set(Array.isArray(n.selectedSkills)?n.selectedSkills:[])),w(Array.isArray(n.searchPaths)?n.searchPaths:[])}catch(e){t||A(e instanceof Error?e.message:d(`failedToLoadSkills`))}finally{t||E(!1)}})(),()=>{t=!0}},[e,a,j]),(0,p.useEffect)(()=>{if(!e||c)return;let t=e=>{e.key===`Escape`&&(M?B():D||V())};return document.addEventListener(`keydown`,t),()=>document.removeEventListener(`keydown`,t)},[e,V,c,D,M,B]);let H=(0,p.useMemo)(()=>{let e=x.trim().toLowerCase();return e?_.filter(t=>[t.name,t.displayName,t.description,t.source,t.compatibility,t.allowedTools,...t.tags??[],...t.triggers??[]].filter(Boolean).join(` `).toLowerCase().includes(e)):_},[x,_]),U=async e=>{N(e),F(null),z(``),L(!0);try{let t=new URLSearchParams({name:e});j&&a?(t.set(`scope`,`project`),t.set(`projectId`,a.id)):t.set(`scope`,`global`),F(await h(await fetch(`/api/skills/content?${t}`)))}catch(e){z(e instanceof Error?e.message:d(`failedToReadSkill`))}finally{L(!1)}};if(!e||j&&!a)return null;let W=e=>{if(D)return;let t=new Set(y);t.has(e)?t.delete(e):t.add(e),b(t),G(t)},G=async e=>{O(!0),A(``);try{let n=await h(await fetch(j?`/api/skills/project`:`/api/skills/global`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify(j?{projectId:a.id,selectedSkills:[...e]}:{selectedSkills:[...e]})}));g({scope:t,project:j?n.projects?.find(e=>e.id===a.id)??{...a,skills:n.selectedSkills}:void 0,projects:n.projects,selectedSkills:n.selectedSkills})}catch(e){b(y),A(e instanceof Error?e.message:d(`failedToSaveSkills`))}finally{O(!1)}},K=d(j?`projectSkills`:`globalSkills`),q=j?d(`projectSkillsDescription`,{project:a.name}):d(`globalSkillsDescription`),J=(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`div`,{className:`quickforge-settings-divider p-3`,children:(0,m.jsxs)(`div`,{className:`quickforge-settings-inline-field`,children:[(0,m.jsx)(o,{className:`pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/60`}),(0,m.jsx)(`input`,{value:x,onChange:e=>S(e.target.value),placeholder:d(`searchSkills`),className:`quickforge-settings-input`,disabled:T||D})]})}),k?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:k}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsx)(`span`,{className:`quickforge-settings-row-title`,children:d(`availableSkills`)}),(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-info`,children:d(`selectedSkillsCount`,{count:y.size})})]}),(0,m.jsx)(`div`,{className:c?``:`max-h-[46vh] overflow-y-auto`,children:T?(0,m.jsxs)(`div`,{className:`quickforge-settings-empty-row inline-flex items-center gap-2`,children:[(0,m.jsx)(n,{className:`size-4 animate-spin`}),d(`loading`)]}):H.length===0?(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:d(`noMatchingSkills`)}):H.map(e=>{let t=y.has(e.name);return(0,m.jsxs)(`div`,{className:u(`quickforge-settings-list-item`,t&&`bg-muted/12`),children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-main`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-row-title`,children:e.displayName||e.name}),e.description?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:e.description}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-meta`,children:[e.source?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.source}):null,e.compatibility?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.compatibility}):null,e.allowedTools?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.allowedTools}):null,e.tags?.slice(0,5).map(e=>(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e},e))]})]}),(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,m.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":D?`true`:`false`,children:[(0,m.jsx)(`input`,{type:`checkbox`,checked:t,disabled:D,onChange:()=>W(e.name),"aria-label":t?`Disable ${e.name}`:`Enable ${e.name}`}),(0,m.jsx)(`span`,{"aria-hidden":`true`})]}),(0,m.jsx)(`button`,{type:`button`,className:`quickforge-settings-icon-action`,onClick:()=>void U(e.name),disabled:D||I,title:d(`readSkill`),"aria-label":`${d(`readSkill`)}: ${e.displayName||e.name}`,children:(0,m.jsx)(r,{className:`size-4`})})]})]},e.name)})})]}),Y=(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`quickforge-settings-button quickforge-settings-button-secondary`,onClick:B,disabled:I,children:[(0,m.jsx)(i,{className:`mr-2 size-4`}),d(`backToSkillList`)]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-main min-w-0`,children:(0,m.jsx)(`div`,{className:`quickforge-settings-row-title truncate`,children:P?.displayName||P?.name||M})})]}),(0,m.jsx)(`div`,{className:`p-4`,children:I?(0,m.jsxs)(`div`,{className:`quickforge-settings-empty-row inline-flex items-center gap-2`,children:[(0,m.jsx)(n,{className:`size-4 animate-spin`}),d(`loading`)]}):R?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:R}):P?(0,m.jsxs)(m.Fragment,{children:[P.description?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description mb-3`,children:P.description}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-meta mb-3`,children:[P.version?(0,m.jsxs)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:[`v`,P.version]}):null,P.source?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.source}):null,P.compatibility?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.compatibility}):null,P.allowedTools?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.allowedTools}):null,P.license?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.license}):null,P.tags?.map(e=>(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e},e))]}),P.triggers?.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description mb-3`,children:[`Triggers: `,P.triggers.join(`, `)]}):null,P.instructions?(0,m.jsx)(`pre`,{className:`whitespace-pre-wrap break-words rounded-md border border-border bg-muted/30 px-4 py-3 font-mono text-sm leading-6 text-foreground/80`,children:P.instructions}):(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:d(`noSkillContent`)})]}):null})]});return c?(0,m.jsx)(`div`,{className:`quickforge-settings-stack`,children:(0,m.jsx)(`section`,{className:`quickforge-settings-section`,"aria-label":K,children:M?Y:(0,m.jsxs)(m.Fragment,{children:[C.length?(0,m.jsx)(`div`,{className:`px-5 pt-4`,children:(0,m.jsxs)(`span`,{className:`quickforge-settings-row-description break-all`,children:[d(`skillSearchPaths`),`: `,C.join(` · `)]})}):null,J]})})}):(0,m.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4`,onClick:e=>{e.target===e.currentTarget&&!D&&V()},children:(0,m.jsxs)(`div`,{className:`flex max-h-[85vh] w-full max-w-2xl flex-col rounded-lg border border-border bg-background shadow-quickforge`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[K,(0,m.jsx)(l,{label:q})]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:q}),C.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description break-all`,children:[d(`skillSearchPaths`),`: `,C.join(` · `)]}):null]}),(0,m.jsx)(`button`,{type:`button`,className:`quickforge-settings-icon-action`,onClick:V,disabled:D,"aria-label":d(`close`),children:(0,m.jsx)(s,{className:`size-4`})})]}),(0,m.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto`,children:M?Y:J})]})})}function _({open:e,scope:t,project:n,onOpenChange:r,onSaved:i}){return(0,m.jsx)(g,{active:e,scope:t,project:n,onClose:()=>r(!1),onSaved:i})}export{f as n,_ as t};
|
package/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-title" content="QuickForge" />
|
|
12
12
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
13
13
|
<title>速构 QuickForge</title>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-BFKildY3.js"></script>
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-DWdDZTNf.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/pi-ai-Cr3rpCXT.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/lit-vendor-Dr3cpBGF.js">
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/assets/icons-C7j5jdKo.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-VqdnQHHS.js">
|
|
22
22
|
<link rel="modulepreload" crossorigin href="/assets/logger-B65Akg8A.js">
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/skills-dialog-
|
|
25
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BFKildY3.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/skills-dialog-DcxeFWSt.js">
|
|
25
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CsKSKUn-.css">
|
|
26
26
|
</head>
|
|
27
27
|
<body>
|
|
28
28
|
<div id="root"></div>
|
package/package.json
CHANGED
package/server/agent-manager.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path'
|
|
|
5
5
|
import { Agent } from '@earendil-works/pi-agent-core'
|
|
6
6
|
import { streamSimpleWithAiHttpLogging } from './ai-http-logger.mjs'
|
|
7
7
|
import { loadSkillToolContext, abortRunningCommand } from './tools/index.mjs'
|
|
8
|
-
import { createSkillTools, workspaceTools } from './tools/definitions.mjs'
|
|
8
|
+
import { createSkillTools, globalMemoryTool, workspaceTools } from './tools/definitions.mjs'
|
|
9
9
|
import { createMcpToolDefinitions, isMcpToolName } from './mcp/registry.mjs'
|
|
10
10
|
import { createPluginToolDefinitions, isPluginToolName } from './plugins/registry.mjs'
|
|
11
11
|
import {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import { projectContextFromId, defaultGlobalWorkspaceContext, readProjectConfig } from './project-config.mjs'
|
|
29
29
|
import { ensureStorage, readStore, atomicUpdate, atomicSessionMetadataUpdate, readSessionValue, writeSessionValue, deleteSessionValue, tempAgentsDir } from './storage.mjs'
|
|
30
30
|
import { logger } from './utils/logger.mjs'
|
|
31
|
+
import { getGlobalMemoryRevision, isGlobalMemoryEnabled } from './global-memory.mjs'
|
|
31
32
|
import { buildSystemPrompt, generateAiTitle, generateTitle } from './session-utils.mjs'
|
|
32
33
|
import { restoreReasoningContentInPayload } from './reasoning-cache.mjs'
|
|
33
34
|
import {
|
|
@@ -111,6 +112,10 @@ async function createServerTools(projectId, projectContext, skillsContext, inclu
|
|
|
111
112
|
.filter(isAllowed)
|
|
112
113
|
.map((definition) => wrapToolDefinition(definition, toolContext, toolPermissions))
|
|
113
114
|
|
|
115
|
+
if (!allowedTools && await isGlobalMemoryEnabled()) {
|
|
116
|
+
tools.push(wrapToolDefinition(globalMemoryTool, toolContext, toolPermissions))
|
|
117
|
+
}
|
|
118
|
+
|
|
114
119
|
if (includeWorkspaceTools && projectContext) {
|
|
115
120
|
const definitions = workspaceTools.filter((definition) => includeSubagentTool || definition.name !== 'run_subagent')
|
|
116
121
|
tools.push(...definitions
|
|
@@ -132,13 +137,21 @@ async function createServerTools(projectId, projectContext, skillsContext, inclu
|
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
async function rebuildSessionTools(session) {
|
|
140
|
+
const profileToolNames = Array.isArray(session.agentProfile?.allowedTools) ? session.agentProfile.allowedTools : null
|
|
135
141
|
session.agent.state.tools = await createServerTools(
|
|
136
142
|
session.projectId,
|
|
137
143
|
session.projectContext,
|
|
138
144
|
sessionSkillsContext(session),
|
|
139
145
|
!!session.projectContext,
|
|
140
146
|
createCommandToolPermissions(session),
|
|
141
|
-
|
|
147
|
+
session.agentProfile
|
|
148
|
+
? {
|
|
149
|
+
allowedToolNames: profileToolNames,
|
|
150
|
+
includeSubagentTool: false,
|
|
151
|
+
includeMcpTools: false,
|
|
152
|
+
parentSessionId: session.sessionId,
|
|
153
|
+
}
|
|
154
|
+
: { parentSessionId: session.sessionId },
|
|
142
155
|
)
|
|
143
156
|
}
|
|
144
157
|
|
|
@@ -1314,6 +1327,28 @@ export function touchSession(sessionId) {
|
|
|
1314
1327
|
return false
|
|
1315
1328
|
}
|
|
1316
1329
|
|
|
1330
|
+
function agentProfileSystemPrompt(agentProfile) {
|
|
1331
|
+
return agentProfile?.systemPrompt
|
|
1332
|
+
? `\n\n<agent_profile_instructions>\nAgent Profile: ${agentProfile.label || agentProfile.name}\n${agentProfile.systemPrompt}\n</agent_profile_instructions>`
|
|
1333
|
+
: ''
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
async function resolvedSessionSystemPrompt(projectId, agentProfile) {
|
|
1337
|
+
return `${await buildSystemPrompt(projectId)}${agentProfileSystemPrompt(agentProfile)}`
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
async function refreshMemoryState(session) {
|
|
1341
|
+
const memoryEnabled = await isGlobalMemoryEnabled()
|
|
1342
|
+
const memoryRevision = memoryEnabled ? await getGlobalMemoryRevision() : null
|
|
1343
|
+
if (session.memoryEnabled === memoryEnabled && session.memoryRevision === memoryRevision) return
|
|
1344
|
+
session.memoryEnabled = memoryEnabled
|
|
1345
|
+
session.memoryRevision = memoryRevision
|
|
1346
|
+
if (session.managedSystemPrompt !== false) {
|
|
1347
|
+
session.agent.state.systemPrompt = await resolvedSessionSystemPrompt(session.projectId, session.agentProfile)
|
|
1348
|
+
}
|
|
1349
|
+
await rebuildSessionTools(session)
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1317
1352
|
/**
|
|
1318
1353
|
* Create or retrieve an Agent for a session.
|
|
1319
1354
|
* If the session already has a running agent, return it.
|
|
@@ -1368,7 +1403,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1368
1403
|
globalSkillNames: projectConfig.globalSkills,
|
|
1369
1404
|
projectSkillNames: configuredProject?.skills,
|
|
1370
1405
|
}
|
|
1371
|
-
const profileSystemPrompt = agentProfile
|
|
1406
|
+
const profileSystemPrompt = agentProfileSystemPrompt(agentProfile)
|
|
1372
1407
|
const resolvedSystemPrompt = systemPrompt ?? `${await buildSystemPrompt(projectId)}${profileSystemPrompt}`
|
|
1373
1408
|
|
|
1374
1409
|
let resolvedAgentProfile = agentProfile
|
|
@@ -1429,6 +1464,8 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1429
1464
|
}
|
|
1430
1465
|
}
|
|
1431
1466
|
|
|
1467
|
+
const initialMemoryEnabled = await isGlobalMemoryEnabled()
|
|
1468
|
+
const initialMemoryRevision = initialMemoryEnabled ? await getGlobalMemoryRevision() : null
|
|
1432
1469
|
let session
|
|
1433
1470
|
const agent = new Agent({
|
|
1434
1471
|
initialState: {
|
|
@@ -1455,6 +1492,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1455
1492
|
const isSkillTool = toolName === 'activate_skill' || toolName === 'read_skill_resource'
|
|
1456
1493
|
if (isSkillTool) return undefined
|
|
1457
1494
|
if (profileToolNames && !profileToolNames.includes(toolName)) return { block: true, reason: `Agent profile ${agentProfile.name} is not allowed to use ${toolName}.` }
|
|
1495
|
+
if (toolName === 'manage_global_memory') return undefined
|
|
1458
1496
|
if (toolName === 'run_subagent') {
|
|
1459
1497
|
const requested = context.args?.subagent
|
|
1460
1498
|
const policy = requested && typeof requested === 'object' ? normalizeCapabilityPolicy(requested.capabilityPolicy || 'readonly-research', Array.isArray(requested.tools) ? requested.tools : []) : null
|
|
@@ -1517,6 +1555,9 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1517
1555
|
stateVersion: 0,
|
|
1518
1556
|
lastAutoCompactAt: null,
|
|
1519
1557
|
lastAutoCompactRejected: null,
|
|
1558
|
+
memoryEnabled: initialMemoryEnabled,
|
|
1559
|
+
memoryRevision: initialMemoryRevision,
|
|
1560
|
+
managedSystemPrompt: systemPrompt == null,
|
|
1520
1561
|
/** Track active SSE connections. Only one SSE stream allowed per session to prevent
|
|
1521
1562
|
* connection-pool exhaustion when two browser tabs load the same session. */
|
|
1522
1563
|
sseConnected: false,
|
|
@@ -1864,6 +1905,7 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
1864
1905
|
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes.'), { statusCode: 409 })
|
|
1865
1906
|
}
|
|
1866
1907
|
|
|
1908
|
+
await refreshMemoryState(session)
|
|
1867
1909
|
resetIdleTimer(session)
|
|
1868
1910
|
|
|
1869
1911
|
// Build user message
|
|
@@ -22,6 +22,7 @@ export const commandRestrictedTools = new Set([
|
|
|
22
22
|
'edit_file',
|
|
23
23
|
'run_command',
|
|
24
24
|
'run_subagent',
|
|
25
|
+
'manage_global_memory',
|
|
25
26
|
])
|
|
26
27
|
|
|
27
28
|
export const planAllowedTools = new Set([
|
|
@@ -99,6 +100,9 @@ export function commandToolPermissionError(session, toolName) {
|
|
|
99
100
|
if ((toolName === 'write_file' || toolName === 'edit_file') && permissions.allowEdit === false) {
|
|
100
101
|
return `Command /${session.activeCommandName} does not allow editing files.`
|
|
101
102
|
}
|
|
103
|
+
if (toolName === 'manage_global_memory' && permissions.allowEdit === false) {
|
|
104
|
+
return `Command /${session.activeCommandName} does not allow editing global memory.`
|
|
105
|
+
}
|
|
102
106
|
return null
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { dataDir, readStore } from './storage.mjs'
|
|
5
|
+
|
|
6
|
+
export const GLOBAL_MEMORY_SETTINGS_KEY = 'memory-settings'
|
|
7
|
+
export const GLOBAL_MEMORY_FILE_NAME = 'MEMORY.md'
|
|
8
|
+
export const DEFAULT_GLOBAL_MEMORY_SETTINGS = { enabled: true }
|
|
9
|
+
|
|
10
|
+
const MAX_MEMORY_FILE_BYTES = 16 * 1024
|
|
11
|
+
|
|
12
|
+
let memoryWriteQueue = Promise.resolve()
|
|
13
|
+
|
|
14
|
+
export function normalizeGlobalMemorySettings(value) {
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return { ...DEFAULT_GLOBAL_MEMORY_SETTINGS }
|
|
16
|
+
return { enabled: value.enabled !== false }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function readGlobalMemorySettings() {
|
|
20
|
+
const settings = await readStore('settings')
|
|
21
|
+
return normalizeGlobalMemorySettings(settings?.[GLOBAL_MEMORY_SETTINGS_KEY])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function isGlobalMemoryEnabled() {
|
|
25
|
+
return (await readGlobalMemorySettings()).enabled
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function globalMemoryFilePath(baseDir = dataDir) {
|
|
29
|
+
return path.join(baseDir, GLOBAL_MEMORY_FILE_NAME)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requestError(message, statusCode = 400) {
|
|
33
|
+
const error = new Error(message)
|
|
34
|
+
error.statusCode = statusCode
|
|
35
|
+
return error
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function containsSensitiveMemory(value) {
|
|
39
|
+
const text = String(value || '')
|
|
40
|
+
const patterns = [
|
|
41
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
|
|
42
|
+
/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[A-Z0-9]{16})\b/,
|
|
43
|
+
/\bauthorization\s*:\s*bearer\s+\S+/i,
|
|
44
|
+
/\bcookie\s*:\s*\S+/i,
|
|
45
|
+
/\b(?:api[ _-]?key|access[ _-]?token|refresh[ _-]?token|password|passwd|secret)\s*(?:is|是|为|:|=)\s*\S{8,}/i,
|
|
46
|
+
/(?:^|\n)\s*[A-Z][A-Z0-9_]{2,}\s*=\s*[^\s]{8,}(?:\n|$)/,
|
|
47
|
+
]
|
|
48
|
+
return patterns.some((pattern) => pattern.test(text))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateGlobalMemoryMarkdown(markdown) {
|
|
52
|
+
if (typeof markdown !== 'string') throw requestError('Memory Markdown is required.')
|
|
53
|
+
if (Buffer.byteLength(markdown, 'utf8') > MAX_MEMORY_FILE_BYTES) throw requestError('Global memory exceeds the size limit.')
|
|
54
|
+
if (containsSensitiveMemory(markdown)) throw requestError('Sensitive credentials or secrets cannot be stored in global memory.')
|
|
55
|
+
return markdown
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getGlobalMemoryRevision(options = {}) {
|
|
59
|
+
const file = globalMemoryFilePath(options.dataDir)
|
|
60
|
+
try {
|
|
61
|
+
const content = await fs.readFile(file)
|
|
62
|
+
return createHash('sha256').update(content).digest('hex')
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error?.code === 'ENOENT') return null
|
|
65
|
+
throw error
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function readGlobalMemory(options = {}) {
|
|
70
|
+
const file = globalMemoryFilePath(options.dataDir)
|
|
71
|
+
try {
|
|
72
|
+
const markdown = await fs.readFile(file, 'utf8')
|
|
73
|
+
return { file, markdown }
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error?.code === 'ENOENT') return { file, markdown: '' }
|
|
76
|
+
throw error
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function readGlobalMemoryDocument(options = {}) {
|
|
81
|
+
const memory = await readGlobalMemory(options)
|
|
82
|
+
return {
|
|
83
|
+
enabled: options.enabled === undefined ? await isGlobalMemoryEnabled() : options.enabled,
|
|
84
|
+
markdown: memory.markdown,
|
|
85
|
+
path: '~/.quickforge/MEMORY.md',
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function writeGlobalMemoryMarkdown(markdown, options = {}) {
|
|
90
|
+
const file = globalMemoryFilePath(options.dataDir)
|
|
91
|
+
await fs.mkdir(path.dirname(file), { recursive: true })
|
|
92
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`
|
|
93
|
+
await fs.writeFile(temporary, markdown, 'utf8')
|
|
94
|
+
await fs.rename(temporary, file)
|
|
95
|
+
return file
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function withMemoryWriteLock(operation) {
|
|
99
|
+
const result = memoryWriteQueue.then(operation, operation)
|
|
100
|
+
memoryWriteQueue = result.catch(() => undefined)
|
|
101
|
+
return result
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function assertMemoryEnabled(options) {
|
|
105
|
+
const enabled = options.enabled === undefined ? await isGlobalMemoryEnabled() : options.enabled
|
|
106
|
+
if (!enabled) throw requestError('Global memory is disabled in Settings.', 403)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function saveGlobalMemoryDocument(markdown, options = {}) {
|
|
110
|
+
await assertMemoryEnabled(options)
|
|
111
|
+
const validated = validateGlobalMemoryMarkdown(markdown)
|
|
112
|
+
return withMemoryWriteLock(async () => {
|
|
113
|
+
const file = await writeGlobalMemoryMarkdown(validated, options)
|
|
114
|
+
return {
|
|
115
|
+
enabled: true,
|
|
116
|
+
markdown: validated,
|
|
117
|
+
path: '~/.quickforge/MEMORY.md',
|
|
118
|
+
file,
|
|
119
|
+
chars: validated.length,
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function readGlobalMemoryPrompt(options = {}) {
|
|
125
|
+
if (options.enabled === false) return null
|
|
126
|
+
if (options.enabled === undefined && !(await isGlobalMemoryEnabled())) return null
|
|
127
|
+
const memory = await readGlobalMemory(options)
|
|
128
|
+
return {
|
|
129
|
+
source: '~/.quickforge/MEMORY.md',
|
|
130
|
+
content: memory.markdown.trim() || null,
|
|
131
|
+
enabled: true,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function manageGlobalMemory(params = {}, options = {}) {
|
|
136
|
+
await assertMemoryEnabled(options)
|
|
137
|
+
const action = String(params.action || '').trim().toLowerCase()
|
|
138
|
+
|
|
139
|
+
if (action === 'read') {
|
|
140
|
+
const memory = await readGlobalMemory(options)
|
|
141
|
+
return {
|
|
142
|
+
content: memory.markdown || 'Global user memory is empty.',
|
|
143
|
+
details: {
|
|
144
|
+
action,
|
|
145
|
+
status: memory.markdown ? 'loaded' : 'empty',
|
|
146
|
+
markdown: memory.markdown,
|
|
147
|
+
chars: memory.markdown.length,
|
|
148
|
+
path: '~/.quickforge/MEMORY.md',
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (action === 'write') {
|
|
154
|
+
const saved = await saveGlobalMemoryDocument(params.markdown, options)
|
|
155
|
+
return {
|
|
156
|
+
content: 'Global user memory saved.',
|
|
157
|
+
details: {
|
|
158
|
+
action,
|
|
159
|
+
status: 'saved',
|
|
160
|
+
markdown: saved.markdown,
|
|
161
|
+
chars: saved.chars,
|
|
162
|
+
path: saved.path,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw requestError('Memory action must be read or write.')
|
|
168
|
+
}
|
package/server/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { handleProjectApi } from './routes/project.mjs'
|
|
|
14
14
|
import { handleFilesystemApi, setActiveWorkspaceRootForFilesystem } from './routes/filesystem.mjs'
|
|
15
15
|
import { handleToolApi, handleGetTools } from './routes/tools.mjs'
|
|
16
16
|
import { handleInstructionsApi } from './routes/instructions.mjs'
|
|
17
|
+
import { handleMemoryApi } from './routes/memory.mjs'
|
|
17
18
|
import { handleSkillsApi } from './routes/skills.mjs'
|
|
18
19
|
import { handleAgentApi } from './routes/agent.mjs'
|
|
19
20
|
import { handleAgentProfilesApi } from './routes/agent-profiles.mjs'
|
|
@@ -312,6 +313,12 @@ async function handleApi(req, res, url) {
|
|
|
312
313
|
return
|
|
313
314
|
}
|
|
314
315
|
|
|
316
|
+
// Global user memory
|
|
317
|
+
if (pathname === '/api/memory') {
|
|
318
|
+
await handleMemoryApi(req, res)
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
315
322
|
// Agent profiles
|
|
316
323
|
if (pathname === '/api/agent-profiles' || pathname.startsWith('/api/agent-profiles/')) {
|
|
317
324
|
await handleAgentProfilesApi(req, res, url)
|
|
@@ -6,6 +6,7 @@ import { existsSync, promises as fs } from 'node:fs'
|
|
|
6
6
|
import { ensureProjectCache, readProjectConfigData, atomicProjectConfigUpdate, dataDir, readStore, atomicUpdate } from './storage.mjs'
|
|
7
7
|
import { setWorkspaceRoot, assertDirectory } from './utils/workspace.mjs'
|
|
8
8
|
import { loadSelectedGlobalSkills, loadSelectedProjectSkills, mergeSkills } from './skills.mjs'
|
|
9
|
+
import { readGlobalMemoryPrompt } from './global-memory.mjs'
|
|
9
10
|
|
|
10
11
|
let defaultWorkspaceRoot = ''
|
|
11
12
|
|
|
@@ -403,6 +404,7 @@ export async function buildInstructionsPayload(projectId) {
|
|
|
403
404
|
? await loadSelectedProjectSkills(project.skills, project.path)
|
|
404
405
|
: []
|
|
405
406
|
const activeSkills = mergeSkills(globalSkills, projectSkills)
|
|
407
|
+
const globalMemory = await readGlobalMemoryPrompt()
|
|
406
408
|
const stripRuntimeFields = ({ rootDir: _rootDir, instructions: _instructions, location: _location, ...skill }) => skill
|
|
407
409
|
|
|
408
410
|
return {
|
|
@@ -417,6 +419,7 @@ export async function buildInstructionsPayload(projectId) {
|
|
|
417
419
|
project: projectInstructions,
|
|
418
420
|
globalSources: globalInstructionSources,
|
|
419
421
|
projectSources: projectInstructionSources,
|
|
422
|
+
globalMemory,
|
|
420
423
|
globalSkills: globalSkills.map(stripRuntimeFields),
|
|
421
424
|
projectSkills: projectSkills.map(stripRuntimeFields),
|
|
422
425
|
skills: activeSkills.map(stripRuntimeFields),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readJsonBody, sendJson } from '../utils/response.mjs'
|
|
2
|
+
import { readGlobalMemoryDocument, saveGlobalMemoryDocument } from '../global-memory.mjs'
|
|
3
|
+
|
|
4
|
+
const MAX_MEMORY_REQUEST_BYTES = 20 * 1024
|
|
5
|
+
|
|
6
|
+
export async function handleMemoryApi(req, res, options = {}) {
|
|
7
|
+
if (req.method === 'GET') {
|
|
8
|
+
sendJson(res, 200, await readGlobalMemoryDocument(options))
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (req.method === 'PUT') {
|
|
13
|
+
const body = await readJsonBody(req, MAX_MEMORY_REQUEST_BYTES)
|
|
14
|
+
if (!body || typeof body.markdown !== 'string') {
|
|
15
|
+
const error = new Error('Memory Markdown is required.')
|
|
16
|
+
error.statusCode = 400
|
|
17
|
+
throw error
|
|
18
|
+
}
|
|
19
|
+
sendJson(res, 200, await saveGlobalMemoryDocument(body.markdown, options))
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const error = new Error('Method not allowed')
|
|
24
|
+
error.statusCode = 405
|
|
25
|
+
throw error
|
|
26
|
+
}
|
package/server/routes/tools.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
2
|
import { readStore } from '../storage.mjs'
|
|
3
3
|
import { toolHandlers, loadSkillToolContext } from '../tools/index.mjs'
|
|
4
|
-
import { createSkillTools, workspaceTools } from '../tools/definitions.mjs'
|
|
4
|
+
import { createSkillTools, globalMemoryTool, workspaceTools } from '../tools/definitions.mjs'
|
|
5
|
+
import { isGlobalMemoryEnabled } from '../global-memory.mjs'
|
|
5
6
|
import { createMcpToolDefinitions } from '../mcp/registry.mjs'
|
|
6
7
|
import { callPluginTool, createPluginToolDefinitions, isPluginToolName } from '../plugins/registry.mjs'
|
|
7
8
|
import { safeReadTools } from '../approval-store.mjs'
|
|
8
9
|
import { projectContextFromId, readProjectConfig } from '../project-config.mjs'
|
|
9
10
|
|
|
10
|
-
const directRouteDisabledTools = new Set(['run_subagent'])
|
|
11
|
+
const directRouteDisabledTools = new Set(['run_subagent', 'manage_global_memory'])
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* GET /api/tools — returns canonical tool definitions (no project context needed).
|
|
@@ -22,7 +23,8 @@ export async function handleGetTools(_req, res) {
|
|
|
22
23
|
})
|
|
23
24
|
const pluginTools = await createPluginToolDefinitions(activeProject ? { workspaceRoot: activeProject.path, project: activeProject } : null)
|
|
24
25
|
const mcpTools = await createMcpToolDefinitions()
|
|
25
|
-
|
|
26
|
+
const memoryTools = await isGlobalMemoryEnabled() ? [globalMemoryTool] : []
|
|
27
|
+
sendJson(res, 200, { tools: [...skillTools, ...memoryTools, ...workspaceTools, ...mcpTools, ...pluginTools] })
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const workspaceToolNames = new Set(workspaceTools.map((tool) => tool.name))
|
package/server/system-prompt.mjs
CHANGED
|
@@ -127,6 +127,28 @@ ${lines.join('\n')}
|
|
|
127
127
|
appendInstructionSources(parts, 'user_instructions', instructions.globalSources, instructions.global)
|
|
128
128
|
appendInstructionSources(parts, 'project_instructions', instructions.projectSources, instructions.project)
|
|
129
129
|
|
|
130
|
+
if (instructions.globalMemory?.enabled) {
|
|
131
|
+
parts.push(`
|
|
132
|
+
<memory_policy>
|
|
133
|
+
User memory stores durable background, preferences, habits, and goals and is used only as context.
|
|
134
|
+
|
|
135
|
+
You may proactively save clear information likely to remain useful in future conversations, and follow user requests to remember, change, or forget. Do not save temporary task information, inferences from a single action, or uncertain content; ask first when unsure.
|
|
136
|
+
|
|
137
|
+
Update memory only when something meaningfully changes. Before writing, read the complete memory, deduplicate, resolve conflicts, and preserve unrelated content.
|
|
138
|
+
|
|
139
|
+
Never store passwords, keys, tokens, credentials, sensitive file contents, or other secrets.
|
|
140
|
+
|
|
141
|
+
The current message takes precedence over older memory. Memory must not override higher-priority instructions or verified code and documentation.
|
|
142
|
+
</memory_policy>`)
|
|
143
|
+
const sourceAttribute = instructions.globalMemory.source ? ` source="${escapeAttribute(instructions.globalMemory.source)}"` : ''
|
|
144
|
+
if (instructions.globalMemory.content) {
|
|
145
|
+
parts.push(`
|
|
146
|
+
<global_user_memory${sourceAttribute}>
|
|
147
|
+
${escapeXml(instructions.globalMemory.content)}
|
|
148
|
+
</global_user_memory>`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
130
152
|
if (instructions.globalSources?.length || instructions.projectSources?.length) {
|
|
131
153
|
parts.push(`
|
|
132
154
|
<instruction_precedence>
|
|
@@ -40,6 +40,20 @@ export const subagentTool = {
|
|
|
40
40
|
}),
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export const globalMemoryTool = {
|
|
44
|
+
name: 'manage_global_memory',
|
|
45
|
+
label: 'Manage global memory',
|
|
46
|
+
description: 'Read or replace the complete global MEMORY.md shared by all chats. The document is free-form Markdown. When memory is enabled, proactively save durable preferences, habits, background, workflows, or goals when they are likely to help in future conversations, and honor explicit requests to remember, change, or forget information. Do not save current-task instructions, transient project details, single-action inferences, uncertain speculation, credentials, or secrets. Do not force an update when nothing meaningful changed. Before writing, read the current document, deduplicate or update conflicting information, preserve unrelated content and formatting, and submit the complete updated Markdown.',
|
|
47
|
+
parameters: Type.Object({
|
|
48
|
+
action: Type.Union([
|
|
49
|
+
Type.Literal('read'),
|
|
50
|
+
Type.Literal('write'),
|
|
51
|
+
], { description: 'Read the complete document or replace it with complete updated Markdown.' }),
|
|
52
|
+
markdown: Type.Optional(Type.String({ description: 'Complete MEMORY.md content. Required for write; saved exactly as provided.' })),
|
|
53
|
+
}),
|
|
54
|
+
executionMode: 'sequential',
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
export const workspaceTools = [
|
|
44
58
|
subagentTool,
|
|
45
59
|
{
|
package/server/tools/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
readSkillResource,
|
|
15
15
|
} from '../skills.mjs'
|
|
16
16
|
import { getToolWorkspaceRoot } from '../utils/workspace.mjs'
|
|
17
|
+
import { manageGlobalMemory } from '../global-memory.mjs'
|
|
17
18
|
|
|
18
19
|
const require = createRequire(import.meta.url)
|
|
19
20
|
|
|
@@ -1088,7 +1089,12 @@ export async function toolRunCommand(params, context, runtime = {}) {
|
|
|
1088
1089
|
})
|
|
1089
1090
|
}
|
|
1090
1091
|
|
|
1092
|
+
export async function toolManageGlobalMemory(params) {
|
|
1093
|
+
return manageGlobalMemory(params)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1091
1096
|
export const toolHandlers = {
|
|
1097
|
+
manage_global_memory: toolManageGlobalMemory,
|
|
1092
1098
|
read_file: toolReadFile,
|
|
1093
1099
|
grep_files: toolGrepFiles,
|
|
1094
1100
|
write_file: toolWriteFile,
|