Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/scripts/backend-docs.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ test('public API tables enumerate the actual complete command/query contracts',
const commandText = api.split('## v1命令')[1].split('## Execution')[0]
assert.deepEqual(rows(queryText), [...queries].sort())
assert.deepEqual(rows(commandText), operations.filter(name => !queries.includes(name)).sort())
assert.equal(queries.length, 30); assert.equal(rows(commandText).length, 35)
for (const name of ['README.md', 'README.zh.md']) assert.match(read(name), /30查询\/35命令/)
assert.equal(queries.length, 31); assert.equal(rows(commandText).length, 36)
for (const name of ['README.md', 'README.zh.md']) assert.match(read(name), /31查询\/36命令/)
})

test('quickstart JSON, shell syntax and local documentation links stay usable', () => {
Expand Down
37 changes: 25 additions & 12 deletions .github/scripts/backend-runtime.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -239,24 +239,20 @@ test('production backend retains canonical Tasks/auth across restart, keeps cons
database.write(state)
} finally { database.close() }
host = await launch(home, env)
let interruptedRun
let interruptedRun, warning
for (let pass = 0; pass < 150; pass++) {
const chief = await devNext('query', 'chief.state', recoveryProject.id, {})
interruptedRun = chief.result.runs.find(row => row.source?.occurrenceId === 'fixture-inspection-occurrence')
if (interruptedRun?.status === 'interrupted' && releaseInspection) break
warning = chief.result.warnings.find(row => row.kind === 'chief-duration' && row.source.runId === interruptedRun?.id)
if (warning?.delivery && releaseInspection) break
await pause(100)
}
assert.equal(interruptedRun?.status, 'interrupted'); assert.ok(inspectionRequests >= 1)
holdInspection = false; releaseInspection()
let confirmed = false
for (let pass = 0; pass < 100; pass++) {
const chief = await devNext('query', 'chief.state', recoveryProject.id, {})
const run = chief.result.runs.find(row => row.id === interruptedRun.id)
if (run?.exitProof?.confirmed) { confirmed = true; break }
await pause(50)
}
assert.equal(confirmed, true, 'Actual native whenIdle/stop must confirm exit before restart recovery')
assert.equal(interruptedRun?.status, 'running'); assert.ok(warning); assert.ok(inspectionRequests >= 1)
assert.equal(warning.delivery?.kind, 'personal-agent', 'A stalled Chief reaches the original owner agent without another Chief execution')
const warningInbox = await devNext('query', 'inbox.list', recoveryProject.id, { agentId: warning.delivery.agentId })
assert.ok(warningInbox.result.items.some(row => row.id === warning.id && row.kind === 'project.warning'))
await host.close(); host = null
holdInspection = false; releaseInspection()
host = await launch(home, env)
let recovered
for (let pass = 0; pass < 150; pass++) {
Expand All @@ -270,6 +266,23 @@ test('production backend retains canonical Tasks/auth across restart, keeps cons
assert.equal(recovered.runs.find(row => row.id === interruptedRun.id).invocation, 2)
assert.ok(recovered.health.lastSuccessAt); assert.equal(recovered.health.lastAppliedDecisionAt, null)
assert.equal((await devNext('query', 'tasks', recoveryProject.id, {})).result.tasks.length, 0)
holdInspection = true; releaseInspection = null
await devNext('command', 'chief.inspect', recoveryProject.id, {}, 'held-warning-stop')
let stopWarning
for (let pass = 0; pass < 150; pass++) {
const warnings = await devNext('query', 'warnings', recoveryProject.id, {})
stopWarning = warnings.result.items.find(row => !row.historical && row.kind === 'chief-duration')
if (stopWarning && releaseInspection) break
await pause(100)
}
assert.ok(stopWarning)
const pendingStop = devNext('command', 'warning.action', recoveryProject.id, { warningId: stopWarning.id, expectedScope: stopWarning.expectedScope, action: 'stop' }, 'stop-one-chief-invocation')
await pause(100)
holdInspection = false; releaseInspection()
const stopped = await pendingStop
assert.equal(stopped.result.stopped, true, 'Explicit stop confirms actual native execution exit without cancelling Tasks')
const afterStop = await devNext('query', 'chief.state', recoveryProject.id, {})
assert.equal(afterStop.result.runs.find(row => row.id === stopWarning.source.runId).status, 'cancelled')
await host.close(); host = null
const statePath = join(home, 'bot/state.json'), future = JSON.stringify({ version: 99, note: 'Preserve this unsupported guard' })
writeFileSync(statePath, future)
Expand Down
35 changes: 8 additions & 27 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ resource/Harness contracts and authorized acceptance boundaries remain below.

M7.4/M7.5 implementation checkpoints are [archived verbatim](docs/history/2026-11-03-completed-m7-checkpoints.md). Private credentials, revocation, exit proof, same-SHA verification and outstanding mixed-Harness/resource acceptance remain required.

### Historical Codex acceptance

The explicitly authorized 2026-11-01 Codex demonstration and its scope are retained
in [the dated record](docs/history/2026-11-04-agent-foundation-checkpoints.md).
It grants no new deployment, App installation or personal credential authority.
Earlier Codex acceptance is [preserved verbatim](docs/history/2026-11-05-warning-memory-checkpoints.md); it grants no new deployment, App or credential authority.

### Collaboration contract and earlier validation

Expand Down Expand Up @@ -160,19 +156,11 @@ DSH deterministic single-resource return/context coverage. Historical deployment
probe details, including their original limitations, remain in
[execution history](docs/history/2026-11-03-execution-evidence.md#task-scoped-sdk-context-and-resident-chief-memory--2026-11-01).

### Task40 service integration history

Earlier S1 integration evidence is [archived verbatim](docs/history/2026-11-03-service-integration.md). Original frontend Task40/PR48 remains separate work: preserve its original UI/functions and healthy execution; backend source convergence does not authorize merge, deployment or UI takeover.

### Publish the service snapshot in the upstream repository — 2026-11-02

The owner explicitly requested pushing the delivered service to a new branch in
ThinkFlowLab/FlyBot. `delivery/ai-centric-development-service` retains db749e4eb
and its verified1811 production baseline; it does not merge or replace current
upstream main3dde8e14. README checkout targets this repository/branch. This is
a snapshot handoff, not a new main-targeting implementation PR or an upstream
compatibility PASS. Runtime/private configuration and the frontend flow are untouched.
### Service delivery history

Earlier Task40/service publication evidence is [preserved verbatim](docs/history/2026-11-05-warning-memory-checkpoints.md).
Preserve frontend Task40/PR48, original UI and healthy execution. Backend work targets
only the authorized delivery branch, not main, credentials or deployment.

### Role, collaboration and context convergence

Expand All @@ -186,16 +174,7 @@ acceptance; historical fixtures do not establish live-provider or UI completion.
See [agents](docs/subsystems/project-agents.md), [Harness](docs/subsystems/project-harness.md)
and the Project memory/context contracts for the current implementation.

### Completed backend convergence checkpoints

Implementation and validation notes for #2179/#2182/#2185/#2186/#2189 are
[preserved verbatim](docs/history/2026-11-04-project-convergence-checkpoints.md).
Current contracts remain: Chief admits versioned Tasks; dependencies retain their
outcome revisions; transactional storage saves facts and Effects together; scoped
receipts/events do not grant authority. Preserve original Bot/group history ACLs,
cutover identities, unknown-exit barriers and private-context publication approval.
Original host/service deployment and real-provider acceptance remain separate.
See canonical Task, storage, service, compatibility and deployment documentation.
Earlier backend convergence checkpoints are [preserved verbatim](docs/history/2026-11-05-warning-memory-checkpoints.md). Versioned Task/dependency outcomes, transactional facts/Effects, original ACLs and private publication approval remain required; historical deployment is not current acceptance.

### Current delivery guarantees

Expand All @@ -210,3 +189,5 @@ forge review, release/deployment and Chief acceptance remain distinct. Operation
consultation uses scoped personal agents; watch grants no host privileges.

Detailed dated implementation checkpoints are [preserved verbatim](docs/history/2026-11-04-reliability-memory-checkpoints.md); current contracts remain in the owned subsystem docs.

#2518: SDK/inspection/merge elapsed time produces evidence-bearing warnings through one progressive disclosure service. Chief chooses recovery; operational warnings reach existing personal/human inboxes. Unknown exit and current authority remain gates.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ HTTP 入口为 `POST /api/bot`、事件为 `GET /api/project-events`,

| 需要 | 文档 |
| --- | --- |
| 对象、权限、全部30查询/35命令、原生接口、幂等与SSE | [API参考](docs/development-service-api.md) |
| 对象、权限、全部31查询/36命令、原生接口、幂等与SSE | [API参考](docs/development-service-api.md) |
| Chief/角色/Task/Handoff、咨询、个人代理、安全与值守、状态与上下文 | [协作指南](docs/development-service-guide.md) |
| 数据目录、账号平台、模型/SDK/仓库、停止重启、备份升级、排障 | [部署运维](docs/backend-operations.md) |
| 个人Chief、多Bot、群聊、公共Bot、原版安装与桌面功能 | [原版FlyBot](README.native.zh.md) · [English](README.native.md) |
Expand Down
2 changes: 1 addition & 1 deletion README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ HTTP 入口为 `POST /api/bot`、事件为 `GET /api/project-events`,

| 需要 | 文档 |
| --- | --- |
| 对象、权限、全部30查询/35命令、原生接口、幂等与SSE | [API参考](docs/development-service-api.md) |
| 对象、权限、全部31查询/36命令、原生接口、幂等与SSE | [API参考](docs/development-service-api.md) |
| Chief/角色/Task/Handoff、咨询、个人代理、安全与值守、状态与上下文 | [协作指南](docs/development-service-guide.md) |
| 数据目录、账号平台、模型/SDK/仓库、停止重启、备份升级、排障 | [部署运维](docs/backend-operations.md) |
| 个人Chief、多Bot、群聊、公共Bot、原版安装与桌面功能 | [原版FlyBot](README.native.zh.md) · [English](README.native.md) |
Expand Down
2 changes: 2 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,5 @@ follows the root README, operation guide, v1 reference and OpenAPI. The earlier
[collaboration checkpoints are archived verbatim](docs/history/2026-11-05-collaboration-memory-checkpoints.md),
including links to their original detailed histories; they grant no new rollout,
privilege or mission scope and complete no outstanding operational acceptance.

#2518 unifies warning summaries/topics/evidence and hourly SDK/two-hour merge notices; verify independent Chief operational delivery and explicit stop without Task cancellation. Legacy DAG unchanged.
1 change: 1 addition & 0 deletions changelog.d/2518.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Replace SDK, Chief inspection and merge wait hard cutoffs with shared progressive warnings and evidence queries; allow authorized execution-only stops while retaining Task history, memory and exit gates.
3 changes: 2 additions & 1 deletion core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { join } from 'node:path'
import * as diagnostics from './src/host/diagnostics.js'
import * as store from './src/host/store.js'
import * as projectEffects from './src/host/project-effects.js'
import * as projectWarnings from './src/host/project-warnings.js'
import * as projectCompatibility from './src/host/project-compatibility.js'
import * as projectInputs from './src/host/project-inputs.js'
import * as projectSources from './src/host/project-sources.js'
Expand Down Expand Up @@ -84,7 +85,7 @@ export const inject = ['llm', 'tools', 'systemPrompt', 'sessionController', 'ses
// the functions other modules call; they call them through `rt` at run time, never during
// install. Listeners run and tools are listed in registration order, so this order holds:
// usage meters model requests after every listener that rewrites them.
const MODULES = [diagnostics, store, projectEffects, projectCompatibility, projectTaskLifecycle, projectInputs, harness, memberAuth, platformIdentity, projectAccess, projectDevelopment, projectExecutionBoundary, projectResources, projectHarness, projectRuntimeSetup, projectDelivery, projectDelegations, projectAgents, projectCredentials, projectGithubRead, projectPools, projectScheduledTasks, projectSources, projectChiefControl, projectScheduler, workbenchState, authz, personalAgentAuth, models, permissions, turns, busyAck, steeringInterrupt, delivery, roster, folders, agents, prompt, timeContext, tools, decisions, projectState, meetings, chatIndex, groups, roomMembership, reach, tasks, projectBoard, taskDrive, approvals, resume, memory, parts, images, secrets, connectors, connectorAccess, capabilities, schedules, projectAssistant, projectChief, projectTeam, automationDefault, browser, usage, update, dshUpdate, hooks, remoteChat, workflowStore, channel]
const MODULES = [diagnostics, store, projectEffects, projectWarnings, projectCompatibility, projectTaskLifecycle, projectInputs, harness, memberAuth, platformIdentity, projectAccess, projectDevelopment, projectExecutionBoundary, projectResources, projectHarness, projectRuntimeSetup, projectDelivery, projectDelegations, projectAgents, projectCredentials, projectGithubRead, projectPools, projectScheduledTasks, projectSources, projectChiefControl, projectScheduler, workbenchState, authz, personalAgentAuth, models, permissions, turns, busyAck, steeringInterrupt, delivery, roster, folders, agents, prompt, timeContext, tools, decisions, projectState, meetings, chatIndex, groups, roomMembership, reach, tasks, projectBoard, taskDrive, approvals, resume, memory, parts, images, secrets, connectors, connectorAccess, capabilities, schedules, projectAssistant, projectChief, projectTeam, automationDefault, browser, usage, update, dshUpdate, hooks, remoteChat, workflowStore, channel]

export function apply(ctx, config = {}) {
const rt = createRuntime(ctx, config)
Expand Down
10 changes: 7 additions & 3 deletions core/src/host/project-agent-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ async function budgetFor(rt, agent) {
const bot = rt.botOf(agent.botId)
return resolveContextBudget(rt, { model: bot?.model ?? rt.config.projectModel ?? rt.cachedDefault?.() })
}
export async function exchangeProjectContext(rt, agentId, delegationId, { contextRequests = [], memoryUpdates = [], memoryRequests = [] }, snapshot, { authorize, isDelegationActive }) {
export async function exchangeProjectContext(rt, agentId, delegationId, { contextRequests = [], memoryUpdates = [], memoryRequests = [], warningRequests = [] }, snapshot, { authorize, isDelegationActive }) {
const agent = ownEntry(rt.state.projectAgents ?? {}, agentId)
if (!agent || agent.id !== `public-${agent.projectId}` || agent.visibility !== 'public' || !isDelegationActive(agentId, delegationId)) throw forbidden('Chief context delegation is unavailable')
const authority = await authorize(rt.botOf(agent.botId))
if (authority.delegation.readOnly && memoryUpdates.length) throw forbidden('Read-only conversations cannot publish memory')
if (snapshot?.archive?.projectId !== agent.projectId || snapshot.archive.role !== 'chief') throw forbidden('Context belongs to another Project or role')
if (![contextRequests, memoryUpdates, memoryRequests].every(rows => Array.isArray(rows) && rows.length <= 4)) throw fail('Chief context operations exceed their budget')
if (![contextRequests, memoryUpdates, memoryRequests, warningRequests].every(rows => Array.isArray(rows) && rows.length <= 4)) throw fail('Chief context operations exceed their budget')
const budget = await budgetFor(rt, agent)
const reads = contextRequests.length ? readContextArchive(snapshot, contextRequests, { projectId: agent.projectId, role: 'chief' }) : []
for (const request of warningRequests) {
const value = await rt.projectWarnings.query({ ...request, projectId: agent.projectId }, authority.actor)
reads.push({ kind: 'warning', id: request.warningId, ...value })
}
const memories = memoryRequests.map(request => {
if (!rt.botMemoryRecall) throw forbidden('Bot memory is unavailable')
const found = rt.botMemoryRecall(agent.botId, { topic: request.topic, query: request.query, offset: request.offset ?? 0, limit: request.limit ?? 4 })
Expand All @@ -39,7 +43,7 @@ export async function exchangeProjectContext(rt, agentId, delegationId, { contex
}
export async function readProjectAgentContext(rt, bot, body, { authorize, contextExchange, privateNotes, logOf, rooms, chiefMemory }) {
const { agent, actor, delegation } = await authorize(bot)
if (body.contextRequests || body.memoryRequests || body.memoryUpdates) return contextExchange(agent.id, delegation.id, body, agent.lastRequest.context)
if (body.contextRequests || body.memoryRequests || body.memoryUpdates || body.warningRequests) return contextExchange(agent.id, delegation.id, body, agent.lastRequest.context)
const budget = await budgetFor(rt, agent)
const detail = await rt.projectEndpoint('project-detail', { id: agent.projectId }, actor)
const notes = agent.visibility === 'private' && !agent.returnAgentId ? ownEntry(privateNotes(), agent.id) ?? [] : []
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/project-agent-inbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function createProjectAgentInbox(rt, consultations, { now = Date.now, not
const task = ownEntry(rt.state.tasks ?? {}, row.linkedTaskId)
const superseded = row.direction === 'incoming' && state.phase !== 'handled' && !consultationCurrent(rt.state, row, task)
return { version: 'agent-inbox/v1', id: row.id, projectId: row.projectId, agentId: carrier.personalAgentId, participationId: carrier.participationId,
kind: row.direction === 'incoming' ? 'consultation.question' : 'consultation.answer', consultationId: row.id, taskId: row.linkedTaskId ?? null, initiativeTransferred: false, requiresHuman: row.requiresHuman === true,
kind: row.kind === 'warning' ? 'project.warning' : row.direction === 'incoming' ? 'consultation.question' : 'consultation.answer', consultationId: row.id, taskId: row.linkedTaskId ?? null, initiativeTransferred: false, requiresHuman: row.requiresHuman === true,
phase: superseded ? 'superseded' : state.phase, generation: state.generation, messageVersion: version(row), createdAt: row.at, updatedAt: state.updatedAt ?? row.at,
claim: state.phase === 'processing' && state.claim ? { claimId: state.claim.id, generation: state.generation, expiresAt: state.claim.expiresAt } : null,
notification: effect ? { status: effect.status, delivery: effect.receipt?.status ?? null, transport: effect.receipt?.transport ?? null, attempts: effect.executions, nextAt: effect.status === 'pending' ? effect.dueAt : null } : { status: 'mailbox-only', delivery: 'private-mailbox', transport: null, attempts: 0, nextAt: null },
Expand Down
Loading
Loading