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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
### Added

- #327 R1:记忆平面设计锚点。新增 ADR-0008 与 `docs/design/memory-plane-v1.md`,冻结四层所有权、FIFO/LRU/TTL 分工、链式 segment/head/index、召回收据与子 issue DAG。durable-memory 绑定 principal + grant/revocation version + permissionDigest(拒绝自由字符串 scope)。准入硬预算以 UTF-8 bytes 为准,模型 token 只作成本上限。不改变运行时;未接受修订不得被实现 PR 消费。
- #338:iOS SwiftUI 原生客户端(`mobile/ios`)。桌面控制面 HTTP 带 `Authorization: Bearer <boot-token>`;WebSocket 指数退避重连(1s/2s/4s/8s/16s,最多 5 次)并保留 device token;组织页必须显式点选岗位后才能发指令;配对码限 6 位数字;主机与 boot-token 本地持久化。回合仍在电脑上执行。

- #356: design-only multi-agent branch heads and handoff-carried checkpoint refs (`docs/design/memory-branch-handoff-v1.md`). No silent head clobber; handoff does not copy grants. No runtime.

Expand Down
19 changes: 19 additions & 0 deletions mobile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Native RoleWeave clients

Three separate apps. They do not share UI code.

| App | Stack | Open with | Issue |
| --- | --- | --- | --- |
| `ios/` | SwiftUI, iOS 17 | Xcode | #338 |
| `android/` | Kotlin, Jetpack Compose | Android Studio | #339 |
| `harmony/` | ArkTS, ArkUI API 12 | DevEco Studio | #340 |

Each app talks to a **running RoleWeave desktop / hosted entry**:

- `GET /api/mobile/workspace` — org preview
- `POST /phone-link/v1/pair` `{ "code": "123456" }` — pair
- `ws(s)://host/phone-link/phone` — send `command.submit`

Turns execute on the computer. These apps do not run Agents.

This Linux sandbox cannot compile iOS, Android, or HarmonyOS binaries. Open each project on the matching OS.
11 changes: 11 additions & 0 deletions mobile/ios/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# RoleWeave iOS

Native SwiftUI client. Open `RoleWeave.xcodeproj` in Xcode 15+ on a Mac.

1. Select an iPhone simulator or device.
2. Set the host (persisted) in 指令 / 设置 to your RoleWeave URL.
3. For the desktop control plane, paste the boot-token; HTTP calls send `Authorization: Bearer <boot-token>` (`/health` is the only unauthenticated route).
4. Pair with a 6-digit code from the desktop. The socket reconnects with exponential backoff (1s/2s/4s/8s/16s, 5 attempts) and keeps the device token.
5. Select a role on 组织 before sending a command. Turns still run on the computer.

Requires iOS 17. Does not compile on Linux.
177 changes: 177 additions & 0 deletions mobile/ios/RoleWeave.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
A10000000000000000000001 /* RoleWeaveApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoleWeaveApp.swift; sourceTree = "<group>"; };
A10000000000000000000002 /* PhoneSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneSession.swift; sourceTree = "<group>"; };
A10000000000000000000003 /* OrgView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrgView.swift; sourceTree = "<group>"; };
A10000000000000000000004 /* CommandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandView.swift; sourceTree = "<group>"; };
A10000000000000000000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A10000000000000000000007 /* PhoneLinkCodec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneLinkCodec.swift; sourceTree = "<group>"; };
A20000000000000000000001 /* RoleWeaveApp.swift */ = {isa = PBXBuildFile; fileRef = A10000000000000000000001 /* RoleWeaveApp.swift */; };
A20000000000000000000002 /* PhoneSession.swift */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* PhoneSession.swift */; };
A20000000000000000000003 /* OrgView.swift */ = {isa = PBXBuildFile; fileRef = A10000000000000000000003 /* OrgView.swift */; };
A20000000000000000000004 /* CommandView.swift */ = {isa = PBXBuildFile; fileRef = A10000000000000000000004 /* CommandView.swift */; };
A20000000000000000000007 /* PhoneLinkCodec.swift */ = {isa = PBXBuildFile; fileRef = A10000000000000000000007 /* PhoneLinkCodec.swift */; };
A30000000000000000000001 /* RoleWeave */ = {
isa = PBXGroup;
children = (
A10000000000000000000001 /* RoleWeaveApp.swift */,
A10000000000000000000002 /* PhoneSession.swift */,
A10000000000000000000007 /* PhoneLinkCodec.swift */,
A10000000000000000000003 /* OrgView.swift */,
A10000000000000000000004 /* CommandView.swift */,
A10000000000000000000005 /* Info.plist */,
);
path = RoleWeave;
sourceTree = "<group>";
};
A30000000000000000000002 /* Root */ = {
isa = PBXGroup;
children = (
A30000000000000000000001 /* RoleWeave */,
);
sourceTree = "<group>";
};
A40000000000000000000001 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A20000000000000000000001 /* RoleWeaveApp.swift */,
A20000000000000000000002 /* PhoneSession.swift */,
A20000000000000000000007 /* PhoneLinkCodec.swift */,
A20000000000000000000003 /* OrgView.swift */,
A20000000000000000000004 /* CommandView.swift */,
);
runOnlyForDeploymentPostprocessing = 0;
};
A50000000000000000000001 /* RoleWeave */ = {
isa = PBXNativeTarget;
buildConfigurationList = A70000000000000000000002 /* Build */;
buildPhases = (
A40000000000000000000001 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = RoleWeave;
productName = RoleWeave;
productReference = A10000000000000000000006 /* RoleWeave.app */;
productType = "com.apple.product-type.application";
};
A10000000000000000000006 /* RoleWeave.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RoleWeave.app; sourceTree = BUILT_PRODUCTS_DIR; };
A60000000000000000000001 /* Project */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1500;
LastUpgradeCheck = 1500;
TargetAttributes = {
A50000000000000000000001 = {
CreatedOnToolsVersion = 15.0;
};
};
};
buildConfigurationList = A70000000000000000000001 /* Project */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = "zh-Hans";
hasScannedForEncodings = 0;
knownRegions = (
en,
"zh-Hans",
Base,
);
mainGroup = A30000000000000000000002 /* Root */;
productRefGroup = A30000000000000000000002 /* Root */;
projectDirPath = "";
projectRoot = "";
targets = (
A50000000000000000000001 /* RoleWeave */,
);
};
A80000000000000000000001 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_MODULES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
A80000000000000000000002 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_MODULES = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 5.0;
};
name = Release;
};
A80000000000000000000003 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = RoleWeave/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = RoleWeave;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.bytefolk.roleweave;
PRODUCT_NAME = RoleWeave;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
A80000000000000000000004 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = RoleWeave/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.bytefolk.roleweave;
PRODUCT_NAME = RoleWeave;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
A70000000000000000000001 /* Project */ = {
isa = XCConfigurationList;
buildConfigurations = (
A80000000000000000000001 /* Debug */,
A80000000000000000000002 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
A70000000000000000000002 /* Build */ = {
isa = XCConfigurationList;
buildConfigurations = (
A80000000000000000000003 /* Debug */,
A80000000000000000000004 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
};
rootObject = A60000000000000000000001 /* Project */;
}
77 changes: 77 additions & 0 deletions mobile/ios/RoleWeave/CommandView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import SwiftUI

struct CommandView: View {
@EnvironmentObject private var session: PhoneSession
@State private var code = ""
@State private var text = ""

var body: some View {
NavigationStack {
Form {
Section("电脑") {
TextField("RoleWeave 地址", text: $session.host)
.textInputAutocapitalization(.never)
.keyboardType(.URL)
SecureField("boot-token(桌面控制面)", text: $session.bootToken)
.textInputAutocapitalization(.never)
Text(session.status).foregroundStyle(.secondary)
}
if !session.paired {
Section("配对") {
TextField("6 位配对码", text: $code)
.keyboardType(.numberPad)
.onChange(of: code) { _, newValue in
let digits = newValue.filter(\.isNumber)
code = String(digits.prefix(6))
}
Button("配对这台电脑") {
Task { await session.pair(code: code) }
}
.disabled(!PhoneLinkCodec.isValidPairCode(code))
}
} else {
Section("指令") {
if let role = session.selectedRole {
Text("岗位:\(role.name)").foregroundStyle(.secondary)
} else {
Text("请先在组织页点选一个岗位").foregroundStyle(.red)
}
TextField("例如:看一下还开着的 PR", text: $text, axis: .vertical)
.lineLimit(4...8)
Button("发给电脑") {
session.sendCommand(text)
text = ""
}
.disabled(session.selectedRoleId == nil || text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
}
if !session.summary.isEmpty {
Section("结果") { Text(session.summary) }
}
}
.navigationTitle("指令")
}
}
}

struct SettingsView: View {
@EnvironmentObject private var session: PhoneSession

var body: some View {
NavigationStack {
List {
LabeledContent("平台", value: "iOS")
LabeledContent("回合", value: "在电脑上执行")
LabeledContent("组织", value: "只读预览")
Section("连接") {
TextField("RoleWeave 地址", text: $session.host)
.textInputAutocapitalization(.never)
.keyboardType(.URL)
SecureField("boot-token", text: $session.bootToken)
.textInputAutocapitalization(.never)
}
}
.navigationTitle("设置")
}
}
}
37 changes: 37 additions & 0 deletions mobile/ios/RoleWeave/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>zh-Hans</string>
<key>CFBundleDisplayName</key>
<string>RoleWeave</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>RoleWeave</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>UILaunchScreen</key>
<dict/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
</dict>
</plist>
Loading
Loading